Forráskód Böngészése

门店管理完成一半;静态页面已迁移

Huanyi 1 hónapja
szülő
commit
c243197180

+ 2 - 2
.env.development

@@ -1,6 +1,6 @@
 # 页面标题
-VITE_APP_TITLE = 管理后台
-VITE_APP_LOGO_TITLE = 管理后台
+VITE_APP_TITLE = 一站护萌
+VITE_APP_LOGO_TITLE = 一站护萌
 
 # 开发环境配置
 VITE_APP_ENV = 'development'

+ 2 - 2
.env.production

@@ -1,6 +1,6 @@
 # 页面标题
-VITE_APP_TITLE = 管理后台
-VITE_APP_LOGO_TITLE = 管理后台
+VITE_APP_TITLE = 一站护萌
+VITE_APP_LOGO_TITLE = 一站护萌
 
 # 生产环境配置
 VITE_APP_ENV = 'production'

+ 12 - 1
src/api/system/store/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { StoreVO, StoreForm, StoreQuery } from '@/api/system/store/types';
+import { StoreVO, StoreForm, StoreQuery, StoreStatusVO } from '@/api/system/store/types';
 
 /**
  * 查询门店管理列表
@@ -61,3 +61,14 @@ export const delStore = (id: string | number | Array<string | number>) => {
     method: 'delete'
   });
 };
+
+/**
+ * 获取状态列表
+ */
+export const listStoreStatus = (): AxiosPromise<StoreStatusVO[]> => {
+  return request({
+    url: '/system/store/listStatus',
+    method: 'get'
+  });
+};
+

+ 30 - 2
src/api/system/store/types.ts

@@ -72,6 +72,16 @@ export interface StoreVO {
    */
   status: number;
 
+  /**
+   * 服务项目
+   */
+  services: number[];
+
+  /**
+   * 商户ID
+   */
+  tenantId: string;
+
 }
 
 export interface StoreForm extends BaseEntity {
@@ -150,6 +160,21 @@ export interface StoreForm extends BaseEntity {
    */
   latitude?: number;
 
+  /**
+   * 服务项目
+   */
+  services?: number[];
+
+  /**
+   * 商户ID
+   */
+  tenantId?: string;
+
+  /**
+   * 区域ID
+   */
+  regionId?: number;
+
 }
 
 export interface StoreQuery extends PageQuery {
@@ -195,5 +220,8 @@ export interface StoreQuery extends PageQuery {
   params?: any;
 }
 
-
-
+export interface StoreStatusVO {
+  value: number;
+  label: string;
+  style: string;
+}

+ 14 - 34
src/components/PageSelect/index.vue

@@ -1,43 +1,22 @@
 <template>
   <div class="page-select">
-    <el-select
-      v-bind="$attrs"
-      v-model="localValue"
-      :filterable="true"
-      :remote="false"
-      @visible-change="handleVisibleChange"
-      @input="handleInput"
-    >
-      <el-option
-        v-for="item in options"
-        :key="item.value"
-        :label="item.label"
-        :value="item.value"
-      />
+    <el-select v-bind="{ ...$attrs, value: undefined }" v-model="localValue" :filterable="true" :remote="false"
+      @visible-change="handleVisibleChange" @change="handleInput">
+      <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
       <!-- 分页组件 -->
       <div v-if="total > 0" class="brand-pagination">
         <div class="custom-pagination">
-          <span
-            class="page-arrow"
-            :class="{ disabled: currentPage <= 1 }"
-            @click="currentPage > 1 && handlePageChange(currentPage - 1)"
-          >
+          <span class="page-arrow" :class="{ disabled: currentPage <= 1 }"
+            @click="currentPage > 1 && handlePageChange(currentPage - 1)">
             &lt;
           </span>
           <template v-for="page in pageNumbers" :key="page">
-            <span
-              class="page-number"
-              :class="{ active: page === currentPage }"
-              @click="handlePageChange(page)"
-            >
+            <span class="page-number" :class="{ active: page === currentPage }" @click="handlePageChange(page)">
               {{ page }}
             </span>
           </template>
-          <span
-            class="page-arrow"
-            :class="{ disabled: currentPage >= totalPages }"
-            @click="currentPage < totalPages && handlePageChange(currentPage + 1)"
-          >
+          <span class="page-arrow" :class="{ disabled: currentPage >= totalPages }"
+            @click="currentPage < totalPages && handlePageChange(currentPage + 1)">
             &gt;
           </span>
           <span class="total-text">共{{ total }}条</span>
@@ -79,7 +58,8 @@ watch(
   () => props.modelValue,
   (newValue) => {
     localValue.value = newValue;
-  }
+  },
+  { immediate: true }
 );
 
 // 计算属性:总页数
@@ -92,20 +72,20 @@ const pageNumbers = computed(() => {
   const pages = [];
   const total = totalPages.value;
   const current = currentPage.value;
-  
+
   // 生成页码,最多显示5个页码
   let start = Math.max(1, current - 2);
   let end = Math.min(total, start + 4);
-  
+
   // 调整起始页码,确保显示5个页码
   if (end - start < 4) {
     start = Math.max(1, end - 4);
   }
-  
+
   for (let i = start; i <= end; i++) {
     pages.push(i);
   }
-  
+
   return pages;
 });
 

+ 4 - 0
src/types/components.d.ts

@@ -14,6 +14,7 @@ declare module 'vue' {
     DictTag: typeof import('./../components/DictTag/index.vue')['default']
     Editor: typeof import('./../components/Editor/index.vue')['default']
     ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
+    ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElBadge: typeof import('element-plus/es')['ElBadge']
     ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
@@ -61,6 +62,9 @@ declare module 'vue' {
     ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElTextArea: typeof import('element-plus/es')['ElTextArea']
+    ElTimeline: typeof import('element-plus/es')['ElTimeline']
+    ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
+    ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElTree: typeof import('element-plus/es')['ElTree']
     ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']

+ 942 - 0
src/views/archieves/customer/index.vue

@@ -0,0 +1,942 @@
+<template>
+  <div class="page-container">
+    <el-card shadow="never">
+      <template #header>
+        <div class="card-header">
+          <span class="title">用户管理</span>
+          <div class="header-actions">
+            <el-select v-model="searchForm.area" placeholder="所属区域" style="width: 150px; margin-right: 10px" clearable>
+              <el-option label="朝阳区" value="朝阳区" />
+              <el-option label="海淀区" value="海淀区" />
+              <el-option label="浦东新区" value="浦东新区" />
+            </el-select>
+            <el-select v-model="searchForm.station" placeholder="所属站点" style="width: 150px; margin-right: 10px" clearable>
+              <el-option label="三里屯服务站" value="三里屯服务站" />
+              <el-option label="中关村服务站" value="中关村服务站" />
+            </el-select>
+            <el-input v-model="searchForm.keyword" placeholder="搜索姓名/手机号" style="width: 200px; margin-right: 10px;" clearable />
+            <el-button type="primary" icon="Plus" @click="handleAdd">新增用户</el-button>
+          </div>
+        </div>
+      </template>
+
+      <el-table :data="filteredTableData" style="width: 100%" :header-cell-style="{ background: '#f5f7fa' }">
+        <el-table-column label="用户基本信息" width="250">
+          <template #default="scope">
+            <div style="display: flex; align-items: center;">
+              <el-avatar :size="40" :src="scope.row.avatar" style="margin-right: 10px;" />
+              <div>
+                <div style="font-weight: bold;">{{ scope.row.name }}
+                  <el-icon v-if="scope.row.gender === '女'" color="#F56C6C"><Female /></el-icon>
+                  <el-icon v-else color="#409EFF"><Male /></el-icon>
+                </div>
+                <div style="font-size: 12px; color: #999;">{{ scope.row.phone }}</div>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="address" label="住址" show-overflow-tooltip min-width="150" />
+        <el-table-column label="用户标签" width="200">
+          <template #default="scope">
+            <el-tag v-for="tag in scope.row.tags" :key="tag.name" :type="tag.type" effect="light" size="small" style="margin-right: 5px;">{{ tag.name }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="录入信息" width="200">
+          <template #default="scope">
+            <div><el-tag size="small" effect="plain" :type="scope.row.source.includes('平台') ? '' : 'warning'">{{ scope.row.source }}</el-tag></div>
+            <div style="font-size: 12px; color: #999; margin-top: 4px;">Created: {{ scope.row.entryTime }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="订单数量" width="120" align="center" sortable prop="orderCount">
+          <template #default="scope">
+            <div>{{ scope.row.orderCount }}单</div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="petCount" label="关联宠物" width="100" align="center">
+          <template #default="scope">
+            <el-tag size="small" round>{{ scope.row.petCount }}只</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" width="100" align="center">
+          <template #default="scope">
+            <el-switch
+              v-model="scope.row.status"
+              inline-prompt
+              active-text="正常"
+              inactive-text="停用"
+              @change="handleStatusChange(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column prop="remark" label="备注" show-overflow-tooltip />
+        <el-table-column label="操作" width="200" align="center">
+          <template #default="scope">
+            <el-button link type="primary" size="small" @click="handleDetail(scope.row)">详情</el-button>
+            <el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
+            <el-dropdown trigger="click" @command="(cmd) => handleCommand(cmd, scope.row)" style="margin-left: 10px; vertical-align: middle">
+              <el-button link type="primary" size="small">
+                更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
+              </el-button>
+              <template #dropdown>
+                <el-dropdown-menu>
+                  <el-dropdown-item command="remark">添加备注</el-dropdown-item>
+                  <el-dropdown-item :command="scope.row.status ? 'disable' : 'enable'">
+                    {{ scope.row.status ? '停用用户' : '启用用户' }}
+                  </el-dropdown-item>
+                  <el-dropdown-item command="delete" style="color: #F56C6C">删除用户</el-dropdown-item>
+                </el-dropdown-menu>
+              </template>
+            </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="pagination-container">
+        <el-pagination
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :page-sizes="[10, 20, 50, 100]"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="total"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </el-card>
+
+    <!-- User Detail Drawer -->
+    <el-drawer v-model="drawerVisible" title="用户档案详情" size="60%" destroy-on-close>
+      <div class="profile-header">
+        <el-avatar :size="80" :src="currentUser.avatar" />
+        <div class="profile-basic">
+          <div class="name-row">
+            <span class="name">{{ currentUser.name }}</span>
+            <el-tag size="small" :type="currentUser.gender === '公' ? '' : 'danger'" effect="dark" style="margin-left: 10px">
+              {{ currentUser.gender }}
+            </el-tag>
+            <span class="phone">{{ currentUser.phone }}</span>
+          </div>
+          <div class="tags-row" style="margin-top: 8px">
+            <el-tag v-for="tag in currentUser.tags" :key="tag.name" :type="tag.type" effect="light" size="small" style="margin-right: 5px">
+              {{ tag.name }}
+            </el-tag>
+          </div>
+        </div>
+      </div>
+
+      <el-tabs v-model="detailActiveTab" class="profile-tabs">
+        <el-tab-pane label="档案信息" name="info">
+          <div class="section-title">基本信息</div>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="姓名">{{ currentUser.name }}</el-descriptions-item>
+            <el-descriptions-item label="电话">{{ currentUser.phone }}</el-descriptions-item>
+            <el-descriptions-item label="所属区域">{{ currentUser.area || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="所属站点">{{ currentUser.station || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="录入来源">{{ currentUser.source }}</el-descriptions-item>
+            <el-descriptions-item label="录入时间">{{ currentUser.entryTime }}</el-descriptions-item>
+          </el-descriptions>
+
+          <div class="section-title" style="margin-top: 20px">居住信息</div>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="详细住址" :span="2">{{ currentUser.address }}</el-descriptions-item>
+            <el-descriptions-item label="房屋类型">
+              {{ currentUser.houseType === 'stairs' ? '楼梯' : '电梯' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="入门方式">
+              {{ currentUser.entryMethod === 'password' ? '密码开门' : '钥匙开门' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="开门详情" :span="2">
+              {{ currentUser.entryMethod === 'password' ? currentUser.entryPassword : currentUser.keyLocation }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+
+        <el-tab-pane label="宠物列表" name="pets">
+          <div style="margin-bottom: 15px;">
+            <el-button type="primary" size="small" icon="Plus" @click="openAddPet">新增宠物</el-button>
+          </div>
+          <el-table :data="currentPets" border style="width: 100%">
+            <el-table-column label="宠物信息" width="200">
+              <template #default="scope">
+                <div style="display: flex; align-items: center;">
+                  <el-avatar :size="30" src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" style="margin-right: 8px;" />
+                  {{ scope.row.name }}
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="breed" label="品种" />
+            <el-table-column prop="gender" label="性别" width="60" />
+            <el-table-column prop="age" label="年龄" width="60" />
+            <el-table-column prop="status" label="健康状态">
+              <template #default="scope">
+                <el-tag :type="scope.row.status === '健康' ? 'success' : 'warning'" size="small">{{ scope.row.status }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="疫苗接种" width="120" align="center">
+              <template #default="scope">
+                {{ scope.row.vaccine || '-' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="200" align="center">
+              <template #default="scope">
+                <el-button link type="primary" @click="handlePetDetail(scope.row)">详情</el-button>
+                <el-button link type="primary" @click="handlePetEdit(scope.row)">编辑</el-button>
+                <el-button link type="primary" @click="handlePetRemark(scope.row)">备注</el-button>
+                <el-button link type="danger" @click="handlePetDelete(scope.row)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-tab-pane>
+
+        <el-tab-pane label="历史订单" name="orders">
+          <el-table :data="mockOrders" border style="width: 100%">
+            <el-table-column prop="orderNo" label="订单编号" width="180" />
+            <el-table-column prop="service" label="服务项目" />
+            <el-table-column prop="pets" label="服务宠物" />
+            <el-table-column prop="time" label="服务时间" width="180" />
+            <el-table-column prop="status" label="状态" width="100">
+              <template #default="scope">
+                <el-tag type="success" size="small">完成</el-tag>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-tab-pane>
+
+        <el-tab-pane label="档案日志" name="logs">
+          <el-timeline style="margin-top: 10px; padding-left: 5px;">
+            <el-timeline-item
+              v-for="(log, index) in mockLogs"
+              :key="index"
+              :timestamp="log.timestamp"
+              :type="log.type"
+            >
+              {{ log.content }}
+              <div style="font-size: 12px; color: #999; margin-top: 4px">操作人: {{ log.operator }}</div>
+            </el-timeline-item>
+          </el-timeline>
+        </el-tab-pane>
+      </el-tabs>
+    </el-drawer>
+
+    <!-- Add/Edit User Dialog -->
+    <el-dialog v-model="dialogVisible" :title="isEdit ? '编辑用户' : '新增用户'" width="700px" destroy-on-close>
+      <el-form :model="form" label-width="90px" class="user-form">
+        <el-row :gutter="20">
+          <el-col :span="24" style="text-align: center; margin-bottom: 25px;">
+            <el-upload action="#" :show-file-list="false" :auto-upload="false">
+              <el-avatar :size="80" :src="form.avatar || 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'" class="upload-avatar" />
+              <div style="margin-top: 8px; font-size: 12px; color: #409EFF;">点击修改头像</div>
+            </el-upload>
+          </el-col>
+
+          <el-col :span="24"><div class="form-section-header">基本资料</div></el-col>
+          <el-col :span="12">
+            <el-form-item label="录入来源">
+              <el-select v-model="form.source" style="width: 100%" filterable allow-create default-first-option placeholder="请选择或输入">
+                <el-option label="平台录入" value="平台录入" />
+                <el-option label="萌它宠物连锁录入" value="萌它宠物连锁录入" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="所属区域">
+              <el-select v-model="form.area" style="width: 100%" filterable allow-create default-first-option placeholder="请选择或输入">
+                <el-option label="朝阳区" value="朝阳区" />
+                <el-option label="海淀区" value="海淀区" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="姓名" required><el-input v-model="form.name" placeholder="请输入姓名" /></el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="电话" required><el-input v-model="form.phone" placeholder="请输入电话" /></el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="性别">
+              <el-radio-group v-model="form.gender">
+                <el-radio label="男">男</el-radio>
+                <el-radio label="女">女</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24"><div class="form-section-header">居住信息</div></el-col>
+          <el-col :span="24">
+            <el-form-item label="所在地区">
+              <el-cascader
+                v-model="form.region"
+                :options="pcaOptions"
+                placeholder="请选择省/市/区"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="详细住址"><el-input v-model="form.detailAddress" placeholder="请输入街道/门牌号" /></el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="房屋类型">
+              <el-radio-group v-model="form.houseType">
+                <el-radio label="stairs">楼梯</el-radio>
+                <el-radio label="elevator">电梯</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="入门方式">
+              <el-radio-group v-model="form.entryMethod">
+                <el-radio label="password">密码开门</el-radio>
+                <el-radio label="key">钥匙开门</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" v-if="form.entryMethod === 'password'">
+            <el-form-item label="开门密码">
+              <el-input v-model="form.entryPassword" placeholder="请输入密码" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" v-if="form.entryMethod === 'key'">
+            <el-form-item label="钥匙位置">
+              <el-input v-model="form.keyLocation" placeholder="如:地毯下" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24"><div class="form-section-header">其他</div></el-col>
+          <el-col :span="24">
+            <el-form-item label="用户标签">
+              <el-select v-model="selectedTagIds" multiple placeholder="选择标签" style="width: 100%">
+                <el-option v-for="tag in allUserTags" :key="tag.id" :label="tag.name" :value="tag.id">
+                  <el-tag :type="tag.type" effect="light" size="small">{{ tag.name }}</el-tag>
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="备注说明"><el-input type="textarea" v-model="form.remark" rows="3" /></el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <div style="text-align: center; margin-top: 20px;">
+        <el-button @click="dialogVisible = false" size="large" style="width: 120px;">取消</el-button>
+        <el-button type="primary" @click="saveUser" size="large" style="width: 120px;">保存</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- Remark Dialog -->
+    <el-dialog v-model="remarkDialogVisible" title="添加备注" width="400px">
+      <el-input v-model="remarkForm.content" type="textarea" :rows="3" placeholder="请输入备注内容..." />
+      <template #footer>
+            <span class="dialog-footer">
+                <el-button @click="remarkDialogVisible = false">取消</el-button>
+                <el-button type="primary" @click="saveRemark">保存</el-button>
+            </span>
+      </template>
+    </el-dialog>
+
+    <!-- Full Add/Edit Pet Dialog -->
+    <el-dialog v-model="petDialogVisible" :title="petForm.id ? '编辑宠物' : '新增宠物'" width="800px">
+      <el-tabs v-model="petDialogActiveTab">
+        <el-tab-pane label="基本信息" name="basic">
+          <el-form :model="petForm" label-width="100px">
+            <el-row>
+              <el-col :span="24" style="display: flex; justify-content: center; margin-bottom: 20px;">
+                <el-upload
+                  class="avatar-uploader"
+                  action="#"
+                  :show-file-list="false"
+                  :auto-upload="false"
+                  :on-change="handlePetUploadFile"
+                >
+                  <el-avatar v-if="petForm.avatar" :src="petForm.avatar" :size="80" />
+                  <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
+                </el-upload>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="宠物姓名" required><el-input v-model="petForm.name" /></el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="性别">
+                  <el-radio-group v-model="petForm.gender">
+                    <el-radio label="公">公</el-radio>
+                    <el-radio label="母">母</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="品种">
+                  <el-select v-model="petForm.breed" placeholder="请选择品种" style="width: 100%" filterable allow-create default-first-option>
+                    <el-option v-for="breed in petBreeds" :key="breed" :label="breed" :value="breed" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="体型">
+                  <el-select v-model="petForm.size" style="width: 100%">
+                    <el-option label="小型" value="small" />
+                    <el-option label="中型" value="medium" />
+                    <el-option label="大型" value="large" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="体重(kg)"><el-input-number v-model="petForm.weight" :min="0" :precision="1" style="width: 100%" /></el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="年龄(岁)"><el-input-number v-model="petForm.age" :min="0" style="width: 100%" /></el-form-item>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="性格关键词"><el-input v-model="petForm.personality" placeholder="如:活泼、粘人" /></el-form-item>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="萌宠性格"><el-input v-model="petForm.cutePersonality" type="textarea" placeholder="详细描述" /></el-form-item>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="宠物标签">
+                  <el-select v-model="petForm.tags" multiple placeholder="选择标签" style="width: 100%">
+                    <el-option v-for="tag in allPetTags" :key="tag.name" :label="tag.name" :value="tag.name">
+                      <el-tag :type="tag.type" effect="light" size="small">{{ tag.name }}</el-tag>
+                    </el-option>
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-tab-pane>
+        <el-tab-pane label="家庭信息" name="family">
+          <el-form :model="petForm" label-width="120px">
+            <el-form-item label="新来家庭时间">
+              <el-date-picker v-model="petForm.arrivalTime" type="date" placeholder="选择日期" style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="家庭房屋类型">
+              <el-radio-group v-model="petForm.houseType">
+                <el-radio label="stairs">楼梯</el-radio>
+                <el-radio label="elevator">电梯</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="入门方式">
+              <el-radio-group v-model="petForm.entryMethod">
+                <el-radio label="password">密码开门</el-radio>
+                <el-radio label="key">钥匙开门</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="密码" v-if="petForm.entryMethod === 'password'">
+              <el-input v-model="petForm.entryPassword" placeholder="请输入门锁密码" />
+            </el-form-item>
+            <el-form-item label="钥匙位置" v-if="petForm.entryMethod === 'key'">
+              <el-input v-model="petForm.keyLocation" placeholder="请输入钥匙存放位置" />
+            </el-form-item>
+          </el-form>
+        </el-tab-pane>
+        <el-tab-pane label="健康状况" name="health">
+          <el-form :model="petForm" label-width="120px">
+            <el-form-item label="健康状态">
+              <el-radio-group v-model="petForm.healthStatus">
+                <el-radio label="健康">健康</el-radio>
+                <el-radio label="亚健康">亚健康</el-radio>
+                <el-radio label="疾病">疾病</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="是否有攻击倾向">
+              <el-switch v-model="petForm.aggression" active-text="是" inactive-text="否" />
+            </el-form-item>
+            <el-form-item label="疫苗情况">
+              <el-radio-group v-model="petForm.vaccine">
+                <el-radio label="无">无</el-radio>
+                <el-radio label="已打1次">已打1次</el-radio>
+                <el-radio label="已打2次">已打2次</el-radio>
+                <el-radio label="已打3次">已打3次</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="疫苗凭证">
+              <el-upload class="avatar-uploader" action="#" :show-file-list="false" :auto-upload="false" :on-change="handlePetUploadVaccineCert">
+                <img v-if="petForm.vaccineCert" :src="petForm.vaccineCert" class="avatar" style="width: 100px; height: 100px; object-fit: cover;" />
+                <el-icon v-else class="avatar-uploader-icon" style="width: 100px; height: 100px; line-height: 100px;"><Plus /></el-icon>
+              </el-upload>
+            </el-form-item>
+            <el-form-item label="既往病史">
+              <el-input v-model="petForm.medicalHistory" type="textarea" placeholder="如有病史请记录" />
+            </el-form-item>
+            <el-form-item label="过敏史">
+              <el-input v-model="petForm.allergies" type="textarea" placeholder="如有过敏源请记录" />
+            </el-form-item>
+          </el-form>
+        </el-tab-pane>
+      </el-tabs>
+      <template #footer>
+            <span class="dialog-footer">
+                <el-button @click="petDialogVisible = false">取消</el-button>
+                <el-button type="primary" @click="savePet">保存</el-button>
+            </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const currentPage = ref(1)
+const pageSize = ref(10)
+const total = ref(100)
+
+const handleSizeChange = (val) => {
+  console.log(`每页 ${val} 条`)
+}
+const handleCurrentChange = (val) => {
+  console.log(`当前页: ${val}`)
+}
+
+const searchForm = reactive({
+  keyword: '',
+  area: '',
+  station: ''
+})
+
+const dialogVisible = ref(false)
+const drawerVisible = ref(false)
+const remarkDialogVisible = ref(false)
+const petDialogVisible = ref(false)
+const isEdit = ref(false)
+const detailActiveTab = ref('info')
+const petDialogActiveTab = ref('basic')
+
+const selectedTagIds = ref([])
+const currentUser = ref({})
+const currentPets = ref([])
+
+// Mock Data
+const allUserTags = [
+  { id: 1, name: '优质客户', type: 'success' },
+  { id: 2, name: '潜在流失', type: 'warning' },
+  { id: 3, name: '黑名单', type: 'danger' }
+]
+
+const petBreeds = [
+  '金毛', '拉布拉多', '柴犬', '柯基', '哈士奇', '阿拉斯加', '萨摩耶', '边境牧羊犬', '德国牧羊犬', '贵宾犬/泰迪', '比熊', '博美', '雪纳瑞', '法斗', '中华田园犬',
+  '英短', '美短', '布偶猫', '加菲猫', '暹罗猫', '波斯猫', '缅因猫', '中华田园猫'
+]
+
+const allPetTags = [
+  { name: '易过敏', type: 'danger' },
+  { name: '胆小', type: 'warning' },
+  { name: '攻击性', type: 'info' },
+  { name: '粘人', type: 'success' }
+]
+
+const tableData = ref([
+  {
+    id: 101,
+    avatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
+    name: '张先生',
+    phone: '13800138000',
+    gender: '男',
+    address: '北京市朝阳区三里屯',
+    houseType: 'elevator',
+    entryMethod: 'password',
+    entryPassword: '456',
+    keyLocation: '',
+    remark: '经常周末来',
+    tags: [{ name: '优质客户', type: 'success' }],
+    petCount: 2,
+    entryTime: '2025-01-15 10:00:00',
+    source: '平台录入',
+    orderCount: 12,
+    totalAmount: 3580.00,
+    area: '朝阳区',
+    station: '三里屯服务站',
+    status: true
+  },
+  {
+    id: 102,
+    avatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
+    name: '李小姐',
+    phone: '13900139000',
+    gender: '女',
+    address: '上海市浦东新区',
+    houseType: 'stairs',
+    entryMethod: 'key',
+    entryPassword: '',
+    keyLocation: '门口地垫下',
+    remark: '',
+    tags: [],
+    petCount: 0,
+    entryTime: '2025-02-01 14:30:00',
+    source: '萌它宠物连锁录入',
+    orderCount: 0,
+    totalAmount: 0.00,
+    area: '浦东新区',
+    station: '',
+    status: true
+  }
+])
+
+const mockOrders = ref([
+  { orderNo: 'DD20231001001', service: '上门喂养 (标准版)', pets: '旺财', time: '2023-10-01 10:00', amount: '88.00', status: 'completed' },
+  { orderNo: 'DD20230915002', service: '深度洗护套餐', pets: '旺财, 咪咪', time: '2023-09-15 14:00', amount: '158.00', status: 'completed' }
+])
+
+const mockLogs = ref([
+  { content: '用户注册成功', timestamp: '2025-01-15 10:00:00', operator: '系统', type: 'success' },
+  { content: '新增宠物档案 [旺财]', timestamp: '2025-01-15 10:05:00', operator: '张先生', type: 'primary' }
+])
+
+const form = reactive({
+  id: null,
+  avatar: '',
+  name: '',
+  phone: '',
+  gender: '男',
+  address: '',
+  detailAddress: '',
+  region: [],
+  houseType: 'elevator',
+  entryMethod: 'password',
+  entryPassword: '',
+  keyLocation: '',
+  remark: '',
+  source: '平台录入',
+  entryTime: '',
+  area: '',
+  status: true
+})
+
+const petForm = reactive({
+  id: null,
+  avatar: '',
+  name: '',
+  gender: '公',
+  breed: '',
+  age: 1,
+  size: 'small',
+  weight: 5,
+  personality: '',
+  cutePersonality: '',
+  tags: [],
+
+  arrivalTime: '',
+  houseType: 'stairs',
+  entryMethod: 'key',
+  entryPassword: '',
+  keyLocation: '',
+
+  healthStatus: '健康',
+  aggression: false,
+  vaccine: '无',
+  vaccineCert: '',
+  medicalHistory: '',
+  allergies: ''
+})
+
+const remarkForm = reactive({ content: '' })
+
+const filteredTableData = computed(() => {
+  return tableData.value.filter(item => {
+    const matchKey = !searchForm.keyword || item.name.includes(searchForm.keyword) || item.phone.includes(searchForm.keyword)
+    const matchArea = !searchForm.area || item.area === searchForm.area
+    const matchStation = !searchForm.station || item.station === searchForm.station
+    return matchKey && matchArea && matchStation
+  })
+})
+
+const handleAdd = () => {
+  isEdit.value = false
+  selectedTagIds.value = []
+  Object.assign(form, {
+    id: null, avatar: '', name: '', phone: '', gender: '男', address: '', detailAddress: '', region: [], remark: '',
+    houseType: 'elevator', entryMethod: 'password', entryPassword: '', keyLocation: '',
+    source: '平台录入', entryTime: new Date().toLocaleString().replace(/\//g, '-'), area: '', status: true
+  })
+  dialogVisible.value = true
+}
+
+const handleEdit = (row) => {
+  isEdit.value = true
+  Object.assign(form, row)
+  // Mock parsing address to region? For now just keep address string
+  form.detailAddress = row.address // Simplify: edit mode just show full string in detail
+  form.region = []
+
+  selectedTagIds.value = row.tags.map(t => allUserTags.find(at => at.name === t.name)?.id).filter(id => id)
+  dialogVisible.value = true
+}
+
+// Mock PCA Data
+const pcaOptions = [
+  {
+    value: '北京市', label: '北京市',
+    children: [
+      { value: '市辖区', label: '市辖区', children: [ { value: '朝阳区', label: '朝阳区' }, { value: '海淀区', label: '海淀区' } ] }
+    ]
+  },
+  {
+    value: '上海市', label: '上海市',
+    children: [
+      { value: '市辖区', label: '市辖区', children: [ { value: '浦东新区', label: '浦东新区' }, { value: '徐汇区', label: '徐汇区' } ] }
+    ]
+  }
+]
+
+const handleDetail = (row) => {
+  currentUser.value = { ...row }
+  detailActiveTab.value = 'info'
+  // Mock Load Pets
+  if(row.petCount > 0) {
+    currentPets.value = [
+      { id: 1, name: '旺财', breed: '金毛', gender: '公', age: 3, status: '健康', vaccine: '已打3次' },
+      { id: 2, name: '咪咪', breed: '加菲猫', gender: '母', age: 2, status: '健康', vaccine: '无' }
+    ].slice(0, row.petCount)
+  } else {
+    currentPets.value = []
+  }
+  drawerVisible.value = true
+}
+
+const handleRemark = (row) => {
+  currentUser.value = row
+  remarkForm.content = ''
+  remarkDialogVisible.value = true
+}
+
+const saveRemark = () => {
+  if (!remarkForm.content) return ElMessage.warning('请输入内容')
+  mockLogs.value.unshift({
+    content: remarkForm.content,
+    timestamp: new Date().toLocaleString(),
+    operator: '管理员',
+    type: 'warning'
+  })
+  ElMessage.success('备注添加成功')
+  remarkDialogVisible.value = false
+}
+
+const saveUser = () => {
+  if (!form.name) return ElMessage.warning('请输入姓名')
+
+  const newTags = selectedTagIds.value.map(id => allUserTags.find(t => t.id === id))
+
+  const fullAddress = (form.region ? form.region.join('') : '') + form.detailAddress
+  const saveForm = { ...form, address: fullAddress }
+
+  if (isEdit.value) {
+    const idx = tableData.value.findIndex(item => item.id === form.id)
+    if (idx !== -1) Object.assign(tableData.value[idx], { ...saveForm, tags: newTags })
+  } else {
+    tableData.value.push({
+      id: Date.now(),
+      ...saveForm,
+      // entryTime is auto set in handleAdd, or here if we want current time on save
+      entryTime: new Date().toLocaleString().replace(/\//g, '-'),
+      tags: newTags,
+      avatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
+      petCount: 0,
+      orderCount: 0,
+      totalAmount: 0.00
+    })
+  }
+  ElMessage.success('保存成功')
+  dialogVisible.value = false
+}
+
+const handleDelete = (row) => {
+  ElMessageBox.confirm('确认删除该用户档案吗?', '提示', { type: 'warning' }).then(() => {
+    tableData.value = tableData.value.filter(item => item.id !== row.id)
+    ElMessage.success('删除成功')
+  })
+}
+
+const handlePetUploadFile = (file) => {
+  petForm.avatar = URL.createObjectURL(file.raw)
+}
+const handlePetUploadVaccineCert = (file) => {
+  petForm.vaccineCert = URL.createObjectURL(file.raw)
+}
+
+const openAddPet = () => {
+  petDialogActiveTab.value = 'basic'
+  Object.assign(petForm, {
+    id: null, avatar: '', name: '', gender: '公', breed: '', age: 1, size: 'small', weight: 5, ownerId: null,
+    personality: '', cutePersonality: '', tags: [],
+    arrivalTime: '', houseType: 'stairs', entryMethod: 'key', entryPassword: '', keyLocation: '',
+    healthStatus: '健康', aggression: false, vaccine: '无', vaccineCert: '', medicalHistory: '', allergies: ''
+  })
+  petDialogVisible.value = true
+}
+
+const handlePetDetail = (row) => {
+  // Show simple details or full? The dialog is editable, so detail view can just be the grid or a text overview.
+  // For now, let's just make it show the edit dialog but read-only?
+  // Or just alert like before? User didn't specify, but "功能复刻" implies full detail.
+  // The "Pet Archive" has a Drawer for details. I should probably use that or just use the Edit Dialog for now as "Detailed View".
+  // But since "handlePetEdit" exists, maybe "handlePetDetail" isn't fully implemented in UserList yet.
+  // I will stick to existing alert or simple functionality unless asked.
+  ElMessage.info(`查看宠物 [${row.name}] 详情`)
+}
+
+const handlePetEdit = (row) => {
+  petDialogActiveTab.value = 'basic'
+  const defaults = {
+    avatar: '', name: '', gender: '公', breed: '', age: 1, size: 'small', weight: 5,
+    personality: '', cutePersonality: '', tags: [],
+    arrivalTime: '', houseType: 'stairs', entryMethod: 'key', entryPassword: '', keyLocation: '',
+    healthStatus: '健康', aggression: false, vaccine: '无', vaccineCert: '', medicalHistory: '', allergies: ''
+  }
+  Object.assign(petForm, { ...defaults, ...row })
+  petDialogVisible.value = true
+}
+
+const handlePetRemark = (row) => {
+  // Reuse main remark dialog but maybe prefix content?
+  remarkForm.content = `[宠物:${row.name}] `
+  remarkDialogVisible.value = true
+}
+
+const handlePetDelete = (row) => {
+  ElMessageBox.confirm(`确认删除宠物 [${row.name}] 吗?`, '提示', { type: 'warning' }).then(() => {
+    currentPets.value = currentPets.value.filter(p => p.id !== row.id)
+    // Update counts
+    if (currentUser.value.id) {
+      const idx = tableData.value.findIndex(item => item.id === currentUser.value.id)
+      if(idx !== -1) {
+        tableData.value[idx].petCount = currentPets.value.length
+        currentUser.value.petCount = currentPets.value.length
+      }
+    }
+    ElMessage.success('宠物删除成功')
+  })
+}
+
+const handleCommand = (command, row) => {
+  if (command === 'remark') {
+    handleRemark(row)
+  } else if (command === 'delete') {
+    handleDelete(row)
+  } else if (command === 'enable' || command === 'disable') {
+    // Toggle status status relies on row.status being updated or we update it manually here if not using v-model in switch
+    // But since we use switch v-model in table, this command might be redundant if switch is there.
+    // However, user asked for 'Operations can modify status'.
+    // If we strictly follow 'Operations bar has Modify Status', then the Switch column is optional but good UX.
+    // Let's assume the switch is the primary way, butdropdown item toggles it too.
+    row.status = command === 'enable'
+    handleStatusChange(row)
+  }
+}
+
+const handleStatusChange = (row) => {
+  ElMessage.success(`${row.name} 已${row.status ? '启用' : '停用'}`)
+}
+
+const savePet = () => {
+  if(!petForm.name) return ElMessage.warning('请输入宠物昵称')
+
+  if (petForm.id) {
+    // Edit existing
+    const idx = currentPets.value.findIndex(p => p.id === petForm.id)
+    if (idx !== -1) Object.assign(currentPets.value[idx], petForm)
+  } else {
+    // Add new
+    currentPets.value.push({
+      id: Date.now(),
+      ...petForm
+    })
+  }
+
+  // Update main count
+  if (currentUser.value.id) {
+    const idx = tableData.value.findIndex(item => item.id === currentUser.value.id)
+    if(idx !== -1) {
+      tableData.value[idx].petCount = currentPets.value.length
+      currentUser.value.petCount = currentPets.value.length
+    }
+  }
+  ElMessage.success('宠物档案保存成功')
+  petDialogVisible.value = false
+}
+</script>
+
+<style scoped>
+.page-container { padding: 20px; }
+.card-header { display: flex; justify-content: space-between; align-items: center; }
+.title { font-weight: bold; }
+
+.profile-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+  padding-bottom: 20px;
+  border-bottom: 1px solid #f0f0f0;
+}
+.profile-basic {
+  margin-left: 20px;
+}
+.name-row {
+  display: flex;
+  align-items: center;
+}
+.name {
+  font-size: 20px;
+  font-weight: bold;
+  color: #303133;
+}
+.phone {
+  margin-left: 10px;
+  color: #666;
+}
+.section-title {
+  font-size: 16px;
+  font-weight: bold;
+  margin-bottom: 15px;
+  border-left: 4px solid #409EFF;
+  padding-left: 10px;
+  line-height: 1.2;
+}
+
+.form-section-header {
+  font-weight: bold;
+  margin-bottom: 15px;
+  margin-top: 10px;
+  padding-bottom: 5px;
+  border-bottom: 1px dashed #eee;
+  color: #303133;
+}
+.upload-avatar:hover {
+  cursor: pointer;
+  opacity: 0.8;
+}
+.pagination-container {
+  margin-top: 20px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+/* Add Upload Styles */
+.avatar-uploader .el-upload {
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  display: inline-block;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 100px;
+  height: 100px;
+  text-align: center;
+  border: 1px dashed #dcdfe6;
+  border-radius: 4px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  transition: .2s;
+}
+.avatar-uploader-icon:hover {
+  border-color: #409EFF;
+  color: #409EFF;
+}
+.avatar {
+  width: 100px;
+  height: 100px;
+  display: block;
+  border-radius: 4px;
+}
+</style>

+ 633 - 0
src/views/archieves/pet/index.vue

@@ -0,0 +1,633 @@
+<template>
+  <div class="page-container">
+    <el-card shadow="never">
+      <template #header>
+        <div class="card-header">
+          <span class="title">宠物档案</span>
+          <div class="header-actions">
+            <el-input v-model="searchKey" placeholder="搜索宠物名/主人" style="width: 200px; margin-right: 10px" clearable />
+            <el-button type="primary" icon="Plus" @click="handleAdd">新增档案</el-button>
+          </div>
+        </div>
+      </template>
+
+      <el-table :data="filteredTableData" style="width: 100%">
+        <el-table-column label="宠物信息" width="220">
+          <template #default="scope">
+            <div style="display: flex; align-items: center">
+              <el-avatar :size="50" :src="scope.row.avatar" style="margin-right: 10px" />
+              <div>
+                <div style="font-weight: bold">{{ scope.row.name }}</div>
+                <div style="font-size: 12px; color: #999">{{ scope.row.breed }} | {{ scope.row.age }}岁</div>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="gender" label="性别" width="80" align="center">
+          <template #default="scope">
+            <el-tag :type="scope.row.gender === '公' ? '' : 'danger'" effect="plain" size="small">{{ scope.row.gender }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="所属主人" width="180">
+          <template #default="scope">
+            <div>{{ scope.row.ownerName }}</div>
+            <div style="font-size: 12px; color: #666">{{ scope.row.ownerPhone }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="标签" min-width="150">
+          <template #default="scope">
+            <el-tag v-for="tag in scope.row.tags" :key="tag" :type="getTagType(tag)" effect="light" size="small" style="margin-right: 5px">{{
+              tag
+            }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="健康状态" width="100" align="center">
+          <template #default="scope">
+            <el-tag :type="scope.row.healthStatus === '健康' ? 'success' : 'warning'" effect="dark" size="small">{{ scope.row.healthStatus }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="疫苗接种" width="120" align="center">
+          <template #default="scope">
+            {{ scope.row.vaccine || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="200" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click="handleDetail(scope.row)">详情</el-button>
+            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
+            <el-button link type="primary" @click="handleRemark(scope.row)">备注</el-button>
+            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="pagination-container">
+        <el-pagination
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :page-sizes="[10, 20, 50, 100]"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="total"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" title="宠物档案详情" width="800px">
+      <el-tabs v-model="activeTab">
+        <el-tab-pane label="基本信息" name="basic">
+          <el-form :model="form" label-width="100px">
+            <el-row>
+              <el-col :span="24" style="display: flex; justify-content: center; margin-bottom: 20px">
+                <el-upload class="avatar-uploader" action="#" :show-file-list="false" :auto-upload="false" :on-change="handleUploadFile">
+                  <el-avatar v-if="form.avatar" :src="form.avatar" :size="80" />
+                  <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
+                </el-upload>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="宠物姓名" required><el-input v-model="form.name" /></el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="所属主人" required>
+                  <el-select v-model="form.ownerId" placeholder="选择主人" style="width: 100%" filterable>
+                    <el-option v-for="user in userList" :key="user.id" :label="user.name + ' - ' + user.phone" :value="user.id" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="性别">
+                  <el-radio-group v-model="form.gender">
+                    <el-radio label="公">公</el-radio>
+                    <el-radio label="母">母</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="品种">
+                  <el-select v-model="form.breed" placeholder="请选择品种" style="width: 100%" filterable allow-create default-first-option>
+                    <el-option v-for="breed in petBreeds" :key="breed" :label="breed" :value="breed" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="体型">
+                  <el-select v-model="form.size" style="width: 100%">
+                    <el-option label="小型" value="small" />
+                    <el-option label="中型" value="medium" />
+                    <el-option label="大型" value="large" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="体重(kg)"><el-input-number v-model="form.weight" :min="0" :precision="1" style="width: 100%" /></el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="年龄(岁)"><el-input-number v-model="form.age" :min="0" style="width: 100%" /></el-form-item>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="性格关键词"><el-input v-model="form.personality" placeholder="如:活泼、粘人" /></el-form-item>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="萌宠性格"><el-input v-model="form.cutePersonality" type="textarea" placeholder="详细描述" /></el-form-item>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="宠物标签">
+                  <el-select v-model="form.tags" multiple placeholder="选择标签" style="width: 100%">
+                    <el-option v-for="tag in allPetTags" :key="tag.name" :label="tag.name" :value="tag.name">
+                      <el-tag :type="tag.type" effect="light" size="small">{{ tag.name }}</el-tag>
+                    </el-option>
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-tab-pane>
+        <el-tab-pane label="家庭信息" name="family">
+          <el-form :model="form" label-width="120px">
+            <el-form-item label="新来家庭时间">
+              <el-date-picker v-model="form.arrivalTime" type="date" placeholder="选择日期" style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="家庭房屋类型">
+              <el-radio-group v-model="form.houseType">
+                <el-radio label="stairs">楼梯</el-radio>
+                <el-radio label="elevator">电梯</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="入门方式">
+              <el-radio-group v-model="form.entryMethod">
+                <el-radio label="password">密码开门</el-radio>
+                <el-radio label="key">钥匙开门</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="密码" v-if="form.entryMethod === 'password'">
+              <el-input v-model="form.entryPassword" placeholder="请输入门锁密码" />
+            </el-form-item>
+            <el-form-item label="钥匙位置" v-if="form.entryMethod === 'key'">
+              <el-input v-model="form.keyLocation" placeholder="请输入钥匙存放位置" />
+            </el-form-item>
+          </el-form>
+        </el-tab-pane>
+        <el-tab-pane label="健康状况" name="health">
+          <el-form :model="form" label-width="120px">
+            <el-form-item label="健康状态">
+              <el-radio-group v-model="form.healthStatus">
+                <el-radio label="健康">健康</el-radio>
+                <el-radio label="亚健康">亚健康</el-radio>
+                <el-radio label="疾病">疾病</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="是否有攻击倾向">
+              <el-switch v-model="form.aggression" active-text="是" inactive-text="否" />
+            </el-form-item>
+            <el-form-item label="疫苗情况">
+              <el-radio-group v-model="form.vaccine">
+                <el-radio label="无">无</el-radio>
+                <el-radio label="已打1次">已打1次</el-radio>
+                <el-radio label="已打2次">已打2次</el-radio>
+                <el-radio label="已打3次">已打3次</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="疫苗凭证">
+              <el-upload class="avatar-uploader" action="#" :show-file-list="false" :auto-upload="false" :on-change="handleUploadVaccineCert">
+                <img v-if="form.vaccineCert" :src="form.vaccineCert" class="avatar" style="width: 100px; height: 100px; object-fit: cover" />
+                <el-icon v-else class="avatar-uploader-icon" style="width: 100px; height: 100px; line-height: 100px"><Plus /></el-icon>
+              </el-upload>
+            </el-form-item>
+            <el-form-item label="既往病史">
+              <el-input v-model="form.medicalHistory" type="textarea" placeholder="如有病史请记录" />
+            </el-form-item>
+            <el-form-item label="过敏史">
+              <el-input v-model="form.allergies" type="textarea" placeholder="如有过敏源请记录" />
+            </el-form-item>
+          </el-form>
+        </el-tab-pane>
+      </el-tabs>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="saveData">保存</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <!-- Pet Profile Drawer -->
+    <el-drawer v-model="drawerVisible" title="宠物档案详情" size="60%" destroy-on-close>
+      <div class="profile-header">
+        <el-avatar :size="80" :src="currentPet.avatar" />
+        <div class="profile-basic">
+          <div class="name-row">
+            <span class="name">{{ currentPet.name }}</span>
+            <el-tag size="small" :type="currentPet.gender === '公' ? '' : 'danger'" effect="dark" style="margin-left: 10px">
+              {{ currentPet.gender }}
+            </el-tag>
+            <el-tag size="small" effect="plain" type="info" style="margin-left: 5px">{{ currentPet.age }}岁</el-tag>
+          </div>
+          <div class="tags-row" style="margin-top: 8px">
+            <el-tag v-for="tag in currentPet.tags" :key="tag" :type="getTagType(tag)" effect="light" size="small" style="margin-right: 5px">
+              {{ tag }}
+            </el-tag>
+          </div>
+        </div>
+        <div style="margin-left: auto">
+          <el-button type="primary" size="small" plain @click="handleRemark(currentPet)">添加备注</el-button>
+        </div>
+      </div>
+
+      <el-tabs v-model="detailActiveTab" class="profile-tabs">
+        <el-tab-pane label="档案信息" name="info">
+          <div class="section-title">基本信息</div>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="品种">{{ currentPet.breed }}</el-descriptions-item>
+            <el-descriptions-item label="体型">
+              {{ currentPet.size === 'small' ? '小型' : currentPet.size === 'medium' ? '中型' : '大型' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="体重">{{ currentPet.weight }} kg</el-descriptions-item>
+            <el-descriptions-item label="所属主人">{{ currentPet.ownerName }} ({{ currentPet.ownerPhone }})</el-descriptions-item>
+            <el-descriptions-item label="性格关键词">{{ currentPet.personality || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="萌宠性格" :span="2">{{ currentPet.cutePersonality || '-' }}</el-descriptions-item>
+          </el-descriptions>
+
+          <div class="section-title" style="margin-top: 20px">家庭信息</div>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="到家时间">{{ currentPet.arrivalTime || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="房屋类型">
+              {{ currentPet.houseType === 'stairs' ? '楼梯' : '电梯' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="入门方式">
+              {{ currentPet.entryMethod === 'password' ? '密码开门' : '钥匙开门' }}
+            </el-descriptions-item>
+            <el-descriptions-item label="开门详情">
+              {{ currentPet.entryMethod === 'password' ? currentPet.entryPassword : currentPet.keyLocation }}
+            </el-descriptions-item>
+          </el-descriptions>
+
+          <div class="section-title" style="margin-top: 20px">健康状况</div>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="健康状态">
+              <el-tag :type="currentPet.healthStatus === '健康' ? 'success' : 'warning'" size="small">{{ currentPet.healthStatus }}</el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="攻击倾向">
+              <el-tag :type="currentPet.aggression ? 'danger' : 'success'" size="small">{{ currentPet.aggression ? '有' : '无' }}</el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="疫苗情况" :span="2">
+              <div>{{ currentPet.vaccine || '-' }}</div>
+              <div v-if="currentPet.vaccineCert" style="margin-top: 10px">
+                <el-image
+                  style="width: 100px; height: 100px; border-radius: 4px"
+                  :src="currentPet.vaccineCert"
+                  :preview-src-list="[currentPet.vaccineCert]"
+                  fit="cover"
+                />
+              </div>
+            </el-descriptions-item>
+            <el-descriptions-item label="既往病史" :span="2">{{ currentPet.medicalHistory || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="过敏史" :span="2">{{ currentPet.allergies || '-' }}</el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+
+        <el-tab-pane label="历史订单" name="orders">
+          <el-table :data="mockOrders" border style="width: 100%">
+            <el-table-column prop="orderNo" label="订单编号" width="180" />
+            <el-table-column prop="service" label="服务项目" />
+            <el-table-column prop="time" label="服务时间" width="180" />
+            <el-table-column prop="amount" label="金额" width="100" />
+            <el-table-column prop="status" label="状态" width="100">
+              <template #default="scope">
+                <el-tag type="success" size="small">完成</el-tag>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-tab-pane>
+
+        <el-tab-pane label="备注日志" name="logs">
+          <el-timeline style="margin-top: 10px; padding-left: 5px">
+            <el-timeline-item v-for="(activity, index) in mockLogs" :key="index" :timestamp="activity.timestamp" :type="activity.type">
+              {{ activity.content }}
+              <div style="font-size: 12px; color: #999; margin-top: 4px">操作人: {{ activity.operator }}</div>
+            </el-timeline-item>
+          </el-timeline>
+        </el-tab-pane>
+      </el-tabs>
+    </el-drawer>
+
+    <!-- Remark Dialog -->
+    <el-dialog v-model="remarkDialogVisible" title="添加备注" width="400px">
+      <el-input v-model="remarkForm.content" type="textarea" :rows="3" placeholder="请输入备注内容..." />
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="remarkDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="saveRemark">保存</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+
+const searchKey = ref('');
+const currentPage = ref(1);
+const pageSize = ref(10);
+const total = ref(100);
+
+const handleSizeChange = (val) => {
+  console.log(`每页 ${val} 条`);
+};
+const handleCurrentChange = (val) => {
+  console.log(`当前页: ${val}`);
+};
+
+const dialogVisible = ref(false);
+const drawerVisible = ref(false);
+const remarkDialogVisible = ref(false);
+const isEdit = ref(false);
+const activeTab = ref('basic');
+const detailActiveTab = ref('info');
+const currentPet = ref({});
+
+const mockOrders = ref([
+  { orderNo: 'DD20231001001', service: '上门喂养 (标准版)', time: '2023-10-01 10:00', amount: '88.00', status: 'completed' },
+  { orderNo: 'DD20230915002', service: '深度洗护套餐', time: '2023-09-15 14:00', amount: '158.00', status: 'completed' }
+]);
+
+const mockLogs = ref([
+  { content: '因宠物胆小,建议安排资深服务师', timestamp: '2023-10-01 09:00', operator: '客服小美', type: 'warning' },
+  { content: '更新了疫苗接种记录', timestamp: '2023-09-10 11:30', operator: '管理员', type: 'primary' },
+  { content: '创建宠物档案', timestamp: '2023-01-01 10:00', operator: '系统', type: 'info' }
+]);
+
+const remarkForm = reactive({ content: '' });
+
+// Mock Users for selection
+const userList = [
+  { id: 101, name: '张先生', phone: '13800138000' },
+  { id: 102, name: '李小姐', phone: '13900139000' }
+];
+
+const petBreeds = [
+  '金毛',
+  '拉布拉多',
+  '柴犬',
+  '柯基',
+  '哈士奇',
+  '阿拉斯加',
+  '萨摩耶',
+  '边境牧羊犬',
+  '德国牧羊犬',
+  '贵宾犬/泰迪',
+  '比熊',
+  '博美',
+  '雪纳瑞',
+  '法斗',
+  '中华田园犬',
+  '英短',
+  '美短',
+  '布偶猫',
+  '加菲猫',
+  '暹罗猫',
+  '波斯猫',
+  '缅因猫',
+  '中华田园猫'
+];
+
+const allPetTags = [
+  { name: '易过敏', type: 'danger' },
+  { name: '胆小', type: 'warning' },
+  { name: '攻击性', type: 'info' },
+  { name: '粘人', type: 'success' }
+];
+
+const getTagType = (name) => {
+  const tag = allPetTags.find((t) => t.name === name);
+  return tag ? tag.type : 'info';
+};
+
+const tableData = ref([
+  {
+    id: 1,
+    avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
+    name: '旺财',
+    gender: '公',
+    breed: '金毛',
+    age: 3,
+    size: 'large',
+    weight: 30,
+    ownerId: 101,
+    ownerName: '张先生',
+    ownerPhone: '13800138000',
+    tags: ['易过敏', '胆小'],
+    healthStatus: '健康',
+    personality: '活泼',
+    cutePersonality: '超级粘人,喜欢玩球',
+    arrivalTime: '2023-01-01',
+    houseType: 'elevator',
+    entryMethod: 'password',
+    entryPassword: '456',
+    aggression: false,
+    vaccine: '已打3次',
+    vaccineCert: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
+    medicalHistory: '',
+    allergies: '海鲜'
+  }
+]);
+
+const filteredTableData = computed(() => {
+  return tableData.value.filter((item) => !searchKey.value || item.name.includes(searchKey.value) || item.ownerName.includes(searchKey.value));
+});
+
+const form = reactive({
+  id: null,
+  avatar: '',
+  name: '',
+  gender: '公',
+  breed: '',
+  age: 1,
+  size: 'small',
+  weight: 5,
+  ownerId: null,
+  personality: '',
+  cutePersonality: '',
+  tags: [],
+
+  arrivalTime: '',
+  houseType: 'stairs',
+  entryMethod: 'key',
+  entryPassword: '',
+  keyLocation: '',
+
+  healthStatus: '健康',
+  aggression: false,
+  vaccine: '无',
+  vaccineCert: '',
+  medicalHistory: '',
+  allergies: ''
+});
+
+const handleAdd = () => {
+  isEdit.value = false;
+  activeTab.value = 'basic';
+  Object.assign(form, {
+    id: null,
+    avatar: '',
+    name: '',
+    gender: '公',
+    breed: '',
+    age: 1,
+    size: 'small',
+    weight: 5,
+    ownerId: null,
+    personality: '',
+    cutePersonality: '',
+    tags: [],
+    arrivalTime: '',
+    houseType: 'stairs',
+    entryMethod: 'key',
+    entryPassword: '',
+    keyLocation: '',
+    healthStatus: '健康',
+    aggression: false,
+    vaccine: '无',
+    vaccineCert: '',
+    medicalHistory: '',
+    allergies: ''
+  });
+  dialogVisible.value = true;
+};
+
+const handleEdit = (row) => {
+  isEdit.value = true;
+  activeTab.value = 'basic';
+  Object.assign(form, row);
+  dialogVisible.value = true;
+};
+
+const handleDetail = (row) => {
+  currentPet.value = { ...row };
+  drawerVisible.value = true;
+};
+
+const handleRemark = (row) => {
+  currentPet.value = row;
+  remarkForm.content = '';
+  remarkDialogVisible.value = true;
+};
+
+const saveRemark = () => {
+  if (!remarkForm.content) return ElMessage.warning('请输入内容');
+  mockLogs.value.unshift({
+    content: remarkForm.content,
+    timestamp: new Date().toLocaleString(),
+    operator: '当前用户',
+    type: 'primary'
+  });
+  ElMessage.success('备注添加成功');
+  remarkDialogVisible.value = false;
+};
+
+const handleDelete = (row) => {
+  ElMessageBox.confirm('确认删除该宠物档案吗?', '提示', { type: 'warning' }).then(() => {
+    tableData.value = tableData.value.filter((item) => item.id !== row.id);
+    ElMessage.success('删除成功');
+  });
+};
+
+const handleUploadFile = (file) => {
+  form.avatar = URL.createObjectURL(file.raw);
+};
+
+const handleUploadVaccineCert = (file) => {
+  form.vaccineCert = URL.createObjectURL(file.raw);
+};
+
+const saveData = () => {
+  if (!form.name) return ElMessage.warning('请输入宠物姓名');
+  if (!form.ownerId) return ElMessage.warning('请选择所属主人');
+
+  const owner = userList.find((u) => u.id === form.ownerId);
+  const saveData = {
+    ...form,
+    ownerName: owner ? owner.name : 'Unknown',
+    ownerPhone: owner ? owner.phone : ''
+  };
+
+  if (isEdit.value) {
+    const idx = tableData.value.findIndex((item) => item.id === form.id);
+    if (idx !== -1) Object.assign(tableData.value[idx], saveData);
+  } else {
+    tableData.value.push({
+      id: Date.now(),
+      ...saveData
+    });
+  }
+  ElMessage.success('保存成功');
+  dialogVisible.value = false;
+};
+</script>
+
+<script>
+// Separate setup logic needed for function declarations not hoisted? No, script setup handles it.
+// Additional functions
+</script>
+
+<style scoped>
+.page-container {
+  padding: 20px;
+}
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.title {
+  font-weight: bold;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 80px;
+  height: 80px;
+  text-align: center;
+  border: 1px dashed #dcdfe6;
+  border-radius: 50%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.avatar-uploader-icon:hover {
+  border-color: var(--el-color-primary);
+}
+.profile-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+  padding-bottom: 20px;
+  border-bottom: 1px solid #f0f0f0;
+}
+.profile-basic {
+  margin-left: 20px;
+}
+.name-row {
+  display: flex;
+  align-items: center;
+}
+.name {
+  font-size: 20px;
+  font-weight: bold;
+  color: #303133;
+}
+.section-title {
+  font-size: 16px;
+  font-weight: bold;
+  margin-bottom: 15px;
+  border-left: 4px solid #409eff;
+  padding-left: 10px;
+  line-height: 1.2;
+}
+.pagination-container {
+  margin-top: 20px;
+  display: flex;
+  justify-content: flex-end;
+}
+</style>

+ 208 - 0
src/views/archieves/tag/index.vue

@@ -0,0 +1,208 @@
+<template>
+  <div class="page-container">
+    <el-card shadow="never">
+      <template #header>
+        <div class="card-header">
+          <span class="title">标签管理</span>
+        </div>
+      </template>
+
+      <el-tabs v-model="activeTab" class="demo-tabs">
+        <el-tab-pane label="用户标签" name="user">
+          <div class="operation-bar">
+            <el-button type="primary" icon="Plus" @click="handleAdd('user')">新增标签</el-button>
+          </div>
+          <el-table :data="userTags" style="width: 100%; margin-top: 20px" :header-cell-style="{ background: '#f5f7fa' }">
+            <el-table-column label="标签名称" width="200">
+              <template #default="scope">
+                <el-tag :type="scope.row.type" effect="light">{{ scope.row.name }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="description" label="说明" show-overflow-tooltip />
+            <el-table-column label="操作" width="180">
+              <template #default="scope">
+                <el-button link type="primary" @click="handleEdit(scope.row, 'user')">编辑</el-button>
+                <el-button link type="danger" @click="handleDelete(scope.row, 'user')">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          <div class="pagination-container">
+            <el-pagination
+              v-model:current-page="userPage.current"
+              v-model:page-size="userPage.size"
+              :total="userPage.total"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="宠物标签" name="pet">
+          <div class="operation-bar">
+            <el-button type="primary" icon="Plus" @click="handleAdd('pet')">新增标签</el-button>
+          </div>
+          <el-table :data="petTags" style="width: 100%; margin-top: 20px" :header-cell-style="{ background: '#f5f7fa' }">
+            <el-table-column label="标签名称" width="200">
+              <template #default="scope">
+                <el-tag :type="scope.row.type" effect="light">{{ scope.row.name }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="description" label="说明" show-overflow-tooltip />
+            <el-table-column label="操作" width="180">
+              <template #default="scope">
+                <el-button link type="primary" @click="handleEdit(scope.row, 'pet')">编辑</el-button>
+                <el-button link type="danger" @click="handleDelete(scope.row, 'pet')">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          <div class="pagination-container">
+            <el-pagination
+              v-model:current-page="petPage.current"
+              v-model:page-size="petPage.size"
+              :total="petPage.total"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" :title="isEdit ? '编辑标签' : '新增标签'" width="400px">
+      <el-form :model="form" label-width="80px">
+        <el-form-item label="标签名称" required>
+          <el-input v-model="form.name" placeholder="如:非常有耐心" />
+        </el-form-item>
+        <el-form-item label="标签颜色" required>
+          <el-select v-model="form.type" placeholder="Select">
+            <el-option v-for="color in tagColors" :key="color.value" :label="color.label" :value="color.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="描述说明">
+          <el-input v-model="form.description" type="textarea" placeholder="请输入描述说明" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="saveData">保存</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+
+const activeTab = ref('user');
+const dialogVisible = ref(false);
+const isEdit = ref(false);
+const currentType = ref('user'); // 'user' or 'pet'
+
+const userPage = reactive({ current: 1, size: 10, total: 20 });
+const petPage = reactive({ current: 1, size: 10, total: 15 });
+
+const handleSizeChange = (val) => {
+  console.log(`每页 ${val} 条`);
+};
+const handleCurrentChange = (val) => {
+  console.log(`当前页: ${val}`);
+};
+
+const userTags = ref([
+  { id: 1, name: '优质客户', type: 'success', description: '累计消费金额TOP10' },
+  { id: 2, name: '潜在流失', type: 'warning', description: '近半年无消费记录' },
+  { id: 3, name: '黑名单', type: 'danger', description: '有过多次投诉行为' },
+  { id: 4, name: '新手', type: 'primary', description: '注册未满1个月' }
+]);
+
+const petTags = ref([
+  { id: 1, name: '易过敏', type: 'danger', description: '对海鲜、谷物过敏' },
+  { id: 2, name: '胆小', type: 'warning', description: '害怕陌生环境和声音' },
+  { id: 3, name: '攻击性', type: 'info', description: '洗澡时需要戴嘴套' },
+  { id: 4, name: '粘人', type: 'success', description: '非常喜欢和人互动' }
+]);
+
+const tagColors = [
+  { label: '默认蓝', value: 'primary' },
+  { label: '成功绿', value: 'success' },
+  { label: '警告橙', value: 'warning' },
+  { label: '危险红', value: 'danger' },
+  { label: '信息灰', value: 'info' }
+];
+
+const form = reactive({
+  id: null,
+  name: '',
+  type: 'primary',
+  description: ''
+});
+
+const handleAdd = (type) => {
+  isEdit.value = false;
+  currentType.value = type;
+  Object.assign(form, { id: null, name: '', type: 'primary', description: '' });
+  dialogVisible.value = true;
+};
+
+const handleEdit = (row, type) => {
+  isEdit.value = true;
+  currentType.value = type;
+  Object.assign(form, row);
+  dialogVisible.value = true;
+};
+
+const handleDelete = (row, type) => {
+  ElMessageBox.confirm('确认删除该标签吗?', '提示', { type: 'warning' }).then(() => {
+    const targetList = type === 'user' ? userTags : petTags;
+    targetList.value = targetList.value.filter((item) => item.id !== row.id);
+    ElMessage.success('删除成功');
+  });
+};
+
+const saveData = () => {
+  if (!form.name) return ElMessage.warning('请输入标签名称');
+
+  const targetList = currentType.value === 'user' ? userTags : petTags;
+
+  if (isEdit.value) {
+    const idx = targetList.value.findIndex((item) => item.id === form.id);
+    if (idx !== -1) Object.assign(targetList.value[idx], form);
+  } else {
+    targetList.value.push({
+      id: Date.now(),
+      ...form
+    });
+  }
+  ElMessage.success('保存成功');
+  dialogVisible.value = false;
+};
+</script>
+
+<style scoped>
+.page-container {
+  padding: 20px;
+}
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.title {
+  font-weight: bold;
+}
+.title {
+  font-weight: bold;
+}
+.operation-bar {
+  margin-bottom: 10px;
+}
+.pagination-container {
+  margin-top: 20px;
+  display: flex;
+  justify-content: flex-end;
+}
+</style>

+ 125 - 32
src/views/system/store/index.vue

@@ -49,48 +49,87 @@
 
       <el-table v-loading="loading" border :data="storeList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="序号" align="center" prop="id" v-if="true" />
-        <el-table-column label="LOGO" align="center" prop="logoUrl" width="100">
+        <el-table-column label="门店信息" align="left" width="300">
           <template #default="scope">
-            <image-preview :src="scope.row.logoUrl" :width="50" :height="50" />
+            <div class="store-info" style="display: flex; align-items: center; gap: 10px;">
+              <div class="store-logo">
+                <image-preview :src="scope.row.logoUrl" :width="50" :height="50" />
+              </div>
+              <div class="store-details" style="display: flex; flex-direction: column; gap: 6px;">
+                <div class="store-name" style="font-size: 14px; font-weight: 500; color: #303133;">{{ scope.row.name }}</div>
+                <div class="store-categories" style="display: flex; gap: 6px;">
+                  <el-tag size="small" type="warning" effect="plain" v-if="scope.row.tenantName">{{ scope.row.tenantName }}</el-tag>
+                  <el-tag size="small" type="success" effect="plain" v-if="scope.row.tenantCatergoriesName">{{ scope.row.tenantCatergoriesName }}</el-tag>
+                </div>
+              </div>
+            </div>
           </template>
         </el-table-column>
-        <el-table-column label="营业执照" align="center" prop="businessLicenseUrl" width="100">
+        <el-table-column label="资质认证" align="center" width="100">
           <template #default="scope">
-            <image-preview :src="scope.row.businessLicenseUrl" :width="50" :height="50" />
+            <image-preview v-if="scope.row.businessLicenseUrl" :src="scope.row.businessLicenseUrl" :width="40" :height="40" />
+            <span v-else>-</span>
           </template>
         </el-table-column>
-        <el-table-column label="门店名称" align="center" prop="name" />
-        <el-table-column label="商户分类" align="center" prop="tenantCatergories" />
-        <el-table-column label="开始营业时间" align="center" prop="startBusinessTime" width="180">
+        <el-table-column label="服务项目" align="center" width="200">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.startBusinessTime, '{y}-{m}-{d}') }}</span>
+            <div class="services">
+              <el-tag v-for="service in scope.row.services" :key="service" size="small"
+                style="margin-right: 5px; margin-bottom: 5px">
+                {{ getServiceName(service) }}
+              </el-tag>
+            </div>
           </template>
         </el-table-column>
-        <el-table-column label="结束营业时间" align="center" prop="endBusinessTime" width="180">
+        <el-table-column label="归属站点" align="center" width="150">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.endBusinessTime, '{y}-{m}-{d}') }}</span>
+            <div>{{ scope.row.siteName }}</div>
           </template>
         </el-table-column>
-        <el-table-column label="联系人" align="center" prop="contact" />
-        <el-table-column label="联系电话" align="center" prop="contactNumber" />
-        <el-table-column label="有效期至" align="center" prop="validity" width="180">
+        <el-table-column label="营业时间" align="center" width="150">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.validity, '{y}-{m}-{d}') }}</span>
+            <div>{{ formatTime(scope.row.startBusinessTime) }}-{{ formatTime(scope.row.endBusinessTime) }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="门店地址" align="center" width="300">
+          <template #default="scope">
+            <div>{{ scope.row.detailAddress }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="联系方式" align="left" width="180">
+          <template #default="scope">
+            <div style="display: flex; flex-direction: column; gap: 6px;">
+              <div style="display: flex; align-items: center; gap: 6px; color: #606266; font-size: 14px;">
+                <el-icon size="16"><User /></el-icon>
+                <span>{{ scope.row.contact }}</span>
+              </div>
+              <div style="display: flex; align-items: center; gap: 6px; color: #409eff; font-size: 14px;">
+                <el-icon size="16"><Phone /></el-icon>
+                <span>{{ scope.row.contactNumber }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="有效期至" align="center" width="120">
+          <template #default="scope">
+            <div>{{ parseTime(scope.row.validity, '{y}-{m}-{d}') }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" width="100">
+          <template #default="scope">
+            <template v-for="item in statusList" :key="item.value">
+              <el-tag v-if="scope.row.status === item.value" :type="item.style">{{ item.label }}</el-tag>
+            </template>
           </template>
         </el-table-column>
-        <el-table-column label="归属站点" align="center" prop="site" />
-        <el-table-column label="详细地址" align="center" prop="detailAddress" />
-        <el-table-column label="状态" align="center" prop="status" />
         <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
           <template #default="scope">
             <el-tooltip content="修改" placement="top">
               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
                 v-hasPermi="['system:store:edit']"></el-button>
             </el-tooltip>
-            <el-tooltip content="删除" placement="top">
-              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
-                v-hasPermi="['system:store:remove']"></el-button>
+            <el-tooltip content="更多" placement="top">
+              <el-button link type="primary" icon="More"></el-button>
             </el-tooltip>
           </template>
         </el-table-column>
@@ -133,17 +172,17 @@
         <el-form-item label="营业时间" prop="startBusinessTime">
           <el-row :gutter="10">
             <el-col :span="10">
-              <el-date-picker clearable v-model="form.startBusinessTime" type="datetime"
-                value-format="YYYY-MM-DD HH:mm:ss" placeholder="开始时间" style="width: 100%">
-              </el-date-picker>
+              <el-time-picker clearable v-model="form.startBusinessTime" value-format="HH:mm" placeholder="开始时间"
+                style="width: 100%">
+              </el-time-picker>
             </el-col>
             <el-col :span="4" style="text-align: center; line-height: 40px">
             </el-col>
             <el-col :span="10">
-              <el-date-picker clearable v-model="form.endBusinessTime" type="datetime"
-                value-format="YYYY-MM-DD HH:mm:ss" placeholder="结束时间" style="width: 100%">
-              </el-date-picker>
+              <el-time-picker clearable v-model="form.endBusinessTime" value-format="HH:mm" placeholder="结束时间"
+                style="width: 100%">
+              </el-time-picker>
             </el-col>
           </el-row>
         </el-form-item>
@@ -154,8 +193,8 @@
           <el-input v-model="form.contactNumber" placeholder="请输入联系电话" />
         </el-form-item>
         <el-form-item label="有效期至" prop="validity">
-          <el-date-picker clearable v-model="form.validity" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
-            placeholder="请选择有效期至" style="width: 100%">
+          <el-date-picker clearable v-model="form.validity" type="date" value-format="YYYY-MM-DD" placeholder="请选择有效期至"
+            style="width: 100%">
           </el-date-picker>
         </el-form-item>
         <el-row :gutter="10">
@@ -209,14 +248,14 @@
 </template>
 
 <script setup name="Store" lang="ts">
-import { listStore, getStore, delStore, addStore, updateStore } from '@/api/system/store';
-import { StoreVO, StoreQuery, StoreForm } from '@/api/system/store/types';
+import { listStore, getStore, delStore, addStore, updateStore, listStoreStatus } from '@/api/system/store';
+import { StoreVO, StoreQuery, StoreForm, StoreStatusVO } from '@/api/system/store/types';
 import { listOnStore } from '@/api/system/tenant';
 import { listOnStore as listTenantCategoriesOnStore } from '@/api/system/tenantCategories';
 import { listOnStore as listServiceOnStore } from '@/api/service/list';
 import { listOnStore as listAreaStationOnStore } from '@/api/system/areaStation';
 import { SysAreaStationOnStoreVo } from '@/api/system/areaStation/types';
-import { regionData, CodeToText, TextToCode } from 'element-china-area-data';
+import { regionData, codeToText, textToCode } from 'element-china-area-data';
 import PageSelect from '@/components/PageSelect/index.vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -247,6 +286,7 @@ const brandKeyword = ref(''); // 搜索关键词
 const brandSelectVisible = ref(false); // 品牌选择框可见状态
 const brandTotal = ref(0); // 品牌总数
 const serviceList = ref<any[]>([]); // 服务项目列表
+const statusList = ref<StoreStatusVO[]>([]); // 状态列表
 const tenantCategoriesList = ref<any[]>([]); // 商户分类列表
 const tenantCategoriesTotal = ref(0); // 商户分类总数
 const areaStationList = ref<SysAreaStationOnStoreVo[]>([]); // 区域站点列表
@@ -603,11 +643,64 @@ const handleBrandSelectVisibleChange = (visible: boolean) => {
   }
 };
 
+// 监听省市区选择变化,自动追加到详细地址
+watch(
+  addressCascaderValue,
+  (newValue) => {
+    if (newValue && newValue.length > 0) {
+      // 将选中的省市区文本追加到详细地址
+      const addressText = newValue.map(code => codeToText[code]).join('');
+      if (form.value.detailAddress) {
+        // 如果已有详细地址,将省市区放在前面
+        form.value.detailAddress = addressText + form.value.detailAddress;
+      } else {
+        form.value.detailAddress = addressText;
+      }
+    }
+  },
+  { deep: true }
+);
+
+/** 获取服务项目名称 */
+const getServiceName = (serviceId: number): string => {
+  const service = serviceList.value.find(item => item.id === serviceId);
+  return service ? service.name : String(serviceId);
+};
+
+/** 获取状态列表 */
+const getStatusList = async () => {
+  try {
+    const res: any = await listStoreStatus();
+    // 兼容可能的不同响应体结构
+    statusList.value = res.data || res.rows || res;
+  } catch (error) {
+    console.error('获取状态列表失败:', error);
+  }
+};
+
+/** 格式化时间为时分 */
+const formatTime = (time: string | number): string => {
+  if (!time) return '';
+
+  // 处理时间戳或日期字符串
+  const date = new Date(time);
+
+  // 检查是否是有效日期
+  if (isNaN(date.getTime())) return '';
+
+  // 格式化为 HH:mm
+  const hours = date.getHours().toString().padStart(2, '0');
+  const minutes = date.getMinutes().toString().padStart(2, '0');
+
+  return `${hours}:${minutes}`;
+};
+
 onMounted(() => {
   getList();
   getBrandList();
   getServiceList();
   getAreaStationList();
+  getStatusList();
 });
 </script>