Bladeren bron

新增团队管理视图,包括团队列表、搜索、编辑和删除功能,使用DataTable组件展示团队信息。

wuyi 4 maanden geleden
bovenliggende
commit
dbfb019128
1 gewijzigde bestanden met toevoegingen van 451 en 0 verwijderingen
  1. 451 0
      src/views/TeamView.vue

+ 451 - 0
src/views/TeamView.vue

@@ -0,0 +1,451 @@
+<template>
+  <div class="rounded-lg p-4 bg-[var(--p-content-background)]">
+    <DataTable
+      :value="tableData.content"
+      :paginator="true"
+      paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown JumpToPageInput"
+      currentPageReportTemplate="{totalRecords} 条记录 "
+      :rows="tableData.metadata.size"
+      :rowsPerPageOptions="[10, 20, 50, 100]"
+      :totalRecords="tableData.metadata.total"
+      @page="handlePageChange"
+      lazy
+      scrollable
+      class="team-table"
+    >
+      <template #header>
+        <div class="flex flex-wrap items-center gap-2">
+          <InputText v-model="searchForm.id" placeholder="ID" size="small" class="w-32" @keyup.enter="handleSearch" />
+          <InputText
+            v-model="searchForm.name"
+            placeholder="团队名称"
+            size="small"
+            class="w-32"
+            @keyup.enter="handleSearch"
+          />
+          <Button icon="pi pi-search" @click="handleSearch" label="搜索" size="small" severity="secondary" />
+          <Button icon="pi pi-refresh" @click="handleRefresh" label="刷新" size="small" />
+          <Button icon="pi pi-plus" @click="openAddDialog" label="新增团队" size="small" severity="success" />
+          <div class="flex-1"></div>
+        </div>
+      </template>
+
+      <Column field="id" header="ID" style="width: 80px" frozen>
+        <template #body="slotProps">
+          <span
+            class="font-mono text-sm copyable-text"
+            :title="slotProps.data.id"
+            @click="copyToClipboard(slotProps.data.id)"
+          >
+            {{ slotProps.data.id }}
+          </span>
+        </template>
+      </Column>
+
+      <Column field="name" header="团队名称" style="min-width: 150px; max-width: 200px">
+        <template #body="slotProps">
+          <span
+            class="font-medium team-name-text copyable-text"
+            :title="slotProps.data.name"
+            @click="copyToClipboard(slotProps.data.name)"
+          >
+            {{ slotProps.data.name }}
+          </span>
+        </template>
+      </Column>
+
+      <Column field="totalRevenue" header="总营收" style="min-width: 120px">
+        <template #body="slotProps">
+          <span class="total-revenue-text font-semibold"> ¥{{ formatAmount(slotProps.data.totalRevenue) }} </span>
+        </template>
+      </Column>
+
+      <Column field="todayRevenue" header="今日营收" style="min-width: 120px">
+        <template #body="slotProps">
+          <span class="today-revenue-text font-semibold"> ¥{{ formatAmount(slotProps.data.todayRevenue) }} </span>
+        </template>
+      </Column>
+
+      <Column field="commissionRate" header="佣金比例" style="min-width: 100px">
+        <template #body="slotProps">
+          <span class="commission-rate-text"> {{ slotProps.data.commissionRate }}% </span>
+        </template>
+      </Column>
+
+      <Column field="createdAt" header="创建时间" style="min-width: 150px">
+        <template #body="slotProps">
+          <span class="text-sm">
+            {{ formatDateTime(slotProps.data.createdAt) }}
+          </span>
+        </template>
+      </Column>
+
+      <Column
+        header="操作"
+        style="min-width: 120px; width: 120px"
+        align-frozen="right"
+        frozen
+        :pt="{
+          columnHeaderContent: {
+            class: 'justify-center'
+          }
+        }"
+      >
+        <template #body="slotProps">
+          <div class="flex justify-center gap-1">
+            <Button
+              icon="pi pi-pencil"
+              severity="info"
+              size="small"
+              text
+              rounded
+              aria-label="编辑"
+              @click="openEditDialog(slotProps.data)"
+            />
+            <Button
+              icon="pi pi-trash"
+              severity="danger"
+              size="small"
+              text
+              rounded
+              aria-label="删除"
+              @click="confirmDelete(slotProps.data)"
+            />
+          </div>
+        </template>
+      </Column>
+    </DataTable>
+
+    <!-- 新增/编辑弹窗 -->
+    <Dialog
+      v-model:visible="editDialog"
+      :modal="true"
+      :header="isEdit ? '编辑团队' : '新增团队'"
+      :style="{ width: '500px' }"
+      position="center"
+    >
+      <div class="p-fluid">
+        <div class="field">
+          <label for="edit-name" class="font-medium text-sm mb-2 block">团队名称</label>
+          <InputText id="edit-name" v-model="editForm.name" class="w-full" />
+        </div>
+
+        <div class="field mt-4">
+          <label for="edit-commissionRate" class="font-medium text-sm mb-2 block">佣金比例</label>
+          <InputNumber
+            id="edit-commissionRate"
+            v-model="editForm.commissionRate"
+            :min="0"
+            :max="100"
+            :step="0.1"
+            suffix=" %"
+            class="w-full"
+          />
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="flex justify-end gap-3">
+          <Button label="取消" severity="secondary" @click="editDialog = false" />
+          <Button label="保存" severity="success" @click="saveEdit" :loading="editLoading" />
+        </div>
+      </template>
+    </Dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useDateFormat } from '@vueuse/core'
+import Button from 'primevue/button'
+import Column from 'primevue/column'
+import DataTable from 'primevue/datatable'
+import Dialog from 'primevue/dialog'
+import InputText from 'primevue/inputtext'
+import InputNumber from 'primevue/inputnumber'
+import { useConfirm } from 'primevue/useconfirm'
+import { useToast } from 'primevue/usetoast'
+import { listTeams, createTeam, updateTeam, deleteTeam } from '@/services/api'
+
+const toast = useToast()
+const confirm = useConfirm()
+
+// 表格数据
+const tableData = ref({
+  content: [],
+  metadata: {
+    page: 0,
+    size: 20,
+    total: 0
+  }
+})
+
+// 加载状态
+const loading = ref(false)
+
+// 编辑相关
+const editDialog = ref(false)
+const editLoading = ref(false)
+const isEdit = ref(false)
+const editForm = ref({
+  id: null,
+  name: null,
+  commissionRate: null
+})
+
+// 搜索表单
+const searchForm = ref({
+  id: null,
+  name: null
+})
+
+// 格式化金额
+const formatAmount = (amount) => {
+  if (!amount) return '0.00'
+  return Number(amount).toFixed(2)
+}
+
+// 格式化日期时间
+const formatDateTime = (dateString) => {
+  if (!dateString) return '-'
+
+  const date = new Date(dateString)
+  const year = date.getFullYear()
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  const hours = String(date.getHours()).padStart(2, '0')
+  const minutes = String(date.getMinutes()).padStart(2, '0')
+  const seconds = String(date.getSeconds()).padStart(2, '0')
+
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+}
+
+// 获取数据
+const fetchData = async () => {
+  loading.value = true
+  try {
+    const response = await listTeams(
+      tableData.value.metadata.page,
+      tableData.value.metadata.size,
+      searchForm.value.name || undefined
+    )
+    tableData.value = response
+  } catch {
+    toast.add({
+      severity: 'error',
+      summary: '错误',
+      detail: '获取团队列表失败',
+      life: 3000
+    })
+  } finally {
+    loading.value = false
+  }
+}
+
+// 分页处理
+const handlePageChange = (event) => {
+  tableData.value.metadata.page = event.page
+  tableData.value.metadata.size = event.rows
+  fetchData()
+}
+
+// 搜索处理
+const handleSearch = () => {
+  tableData.value.metadata.page = 0
+  fetchData()
+}
+
+// 刷新处理
+const handleRefresh = () => {
+  searchForm.value = {
+    id: null,
+    name: null
+  }
+  tableData.value.metadata.page = 0
+  fetchData()
+}
+
+// 确认删除
+const confirmDelete = (team) => {
+  confirm.require({
+    message: `确定要删除团队 "${team.name}" 吗?`,
+    header: '确认删除',
+    icon: 'pi pi-exclamation-triangle',
+    accept: () => deleteTeamRecord(team.id)
+  })
+}
+
+// 删除团队
+const deleteTeamRecord = async (id) => {
+  try {
+    await deleteTeam(id)
+    toast.add({
+      severity: 'success',
+      summary: '成功',
+      detail: '删除成功',
+      life: 3000
+    })
+    fetchData()
+  } catch {
+    toast.add({
+      severity: 'error',
+      summary: '错误',
+      detail: '删除失败',
+      life: 3000
+    })
+  }
+}
+
+// 复制到剪贴板
+const copyToClipboard = async (text) => {
+  try {
+    await navigator.clipboard.writeText(text)
+  } catch {
+    const textArea = document.createElement('textarea')
+    textArea.value = text
+    document.body.appendChild(textArea)
+    textArea.select()
+    document.execCommand('copy')
+    document.body.removeChild(textArea)
+  }
+}
+
+// 打开新增弹窗
+const openAddDialog = () => {
+  isEdit.value = false
+  editForm.value = {
+    id: null,
+    name: null,
+    commissionRate: null
+  }
+  editDialog.value = true
+}
+
+// 打开编辑弹窗
+const openEditDialog = (team) => {
+  isEdit.value = true
+  editForm.value = {
+    id: team.id,
+    name: team.name || null,
+    commissionRate: team.commissionRate || null
+  }
+  editDialog.value = true
+}
+
+// 保存编辑
+const saveEdit = async () => {
+  editLoading.value = true
+  try {
+    // 过滤掉空值参数
+    const formData = {}
+    if (editForm.value.name !== null && editForm.value.name !== '') {
+      formData.name = editForm.value.name
+    }
+    if (editForm.value.commissionRate !== null && editForm.value.commissionRate !== '') {
+      formData.commissionRate = editForm.value.commissionRate
+    }
+
+    if (isEdit.value) {
+      await updateTeam(editForm.value.id, formData)
+    } else {
+      await createTeam(formData)
+    }
+    toast.add({
+      severity: 'success',
+      summary: '成功',
+      detail: isEdit.value ? '更新成功' : '创建成功',
+      life: 3000
+    })
+    editDialog.value = false
+    fetchData()
+  } catch {
+    toast.add({
+      severity: 'error',
+      summary: '错误',
+      detail: isEdit.value ? '更新失败' : '创建失败',
+      life: 3000
+    })
+  } finally {
+    editLoading.value = false
+  }
+}
+
+// 初始化
+onMounted(() => {
+  fetchData()
+})
+</script>
+
+<style scoped>
+.p-datatable-sm .p-datatable-tbody > tr > td {
+  padding: 0.5rem;
+}
+
+.p-datatable-sm .p-datatable-thead > tr > th {
+  padding: 0.5rem;
+}
+
+.team-table {
+  width: 100%;
+}
+
+.team-table .p-datatable-wrapper {
+  overflow-x: auto;
+}
+
+.team-table .p-datatable-thead th {
+  white-space: nowrap;
+  min-width: 100px;
+}
+
+.font-mono {
+  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+}
+
+.team-name-text {
+  max-width: 250px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  display: inline-block;
+}
+
+.total-revenue-text {
+  color: #2563eb;
+  font-weight: 600;
+}
+
+.today-revenue-text {
+  color: #059669;
+  font-weight: 600;
+}
+
+.commission-rate-text {
+  color: #7c3aed;
+  font-weight: 500;
+}
+
+.font-medium {
+  font-weight: 500;
+}
+
+.text-sm {
+  font-size: 0.875rem;
+}
+
+.copyable-text {
+  cursor: pointer;
+  transition: all 0.2s ease;
+  user-select: none;
+}
+
+.copyable-text:hover {
+  background-color: #e5e7eb;
+  border-radius: 4px;
+}
+
+.copyable-text:active {
+  background-color: #d1d5db;
+  transform: scale(0.98);
+}
+</style>