董国庆
2026-03-18 f907866e56d795ff394e7d01dd4c5ffbce33dedd
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/xp-robotaxi-web
1个文件已添加
8个文件已修改
404 ■■■■■ 已修改文件
locales/en_US.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
locales/zh_CN.json 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/public/placeholder.png 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/robotaxi.scss 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/review/personnel/components/ReviewModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/review/personnel/index.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/review/policyApplication/index.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/ticket/feedback/index.vue 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/ticket/feedbackType/index.vue 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
locales/en_US.json
@@ -224,7 +224,7 @@
      "remark": "Remark",
      "chooseResult": "Please choose a result",
      "remarkRequired": "Please enter a remark",
      "save": "Save",
      "submit": "Submit",
      "validateFail": "Validation failed"
    },
    "policyApplication": {
locales/zh_CN.json
@@ -15,6 +15,39 @@
    "expand": "展开",
    "collapse": "收起"
  },
  "ticket": {
    "feedback": {
      "index": "序号",
      "feedbackPerson": "反馈人",
      "feedbackType": "反馈类型",
      "feedbackContent": "反馈内容",
      "image": "图片",
      "contact": "联系方式",
      "feedbackTime": "反馈时间",
      "view": "查看",
      "imagePreview": "图片预览"
    },
    "feedbackType": {
      "index": "序号",
      "feedbackType": "反馈类型",
      "createTime": "创建时间",
      "actions": "操作",
      "add": "新增",
      "edit": "编辑",
      "delete": "删除",
      "confirmDelete": "确认删除",
      "confirmDeleteContent": "确定要删除该反馈类型吗?",
      "addSuccess": "新增成功",
      "addFail": "新增失败",
      "editSuccess": "编辑成功",
      "editFail": "编辑失败",
      "deleteSuccess": "删除成功",
      "deleteFail": "删除失败",
      "inputPlaceholder": "请输入5个字以内",
      "required": "请输入反馈类型",
      "create": "新建"
    }
  },
  "app": {
    "loginOut": "退出登录",
    "loginOutTitle": "退出登录",
@@ -247,7 +280,7 @@
      "remark": "备注",
      "chooseResult": "请选择审核结果",
      "remarkRequired": "请输入备注",
      "save": "保存",
      "submit": "确定",
      "validateFail": "验证失败"
    },
    "policyApplication": {
src/assets/public/placeholder.png
src/styles/robotaxi.scss
@@ -184,6 +184,10 @@
  }
}
.n-checkbox.n-checkbox--disabled .n-checkbox-box{
  background-color: rgba(23, 31, 38, 0.15);
}
// 插槽样式-下拉框
.arrow-icon {
  transition: transform 0.3s ease;
src/views/service/review/personnel/components/ReviewModal.vue
@@ -121,7 +121,7 @@
      <n-space justify="end">
        <n-button @click="handleCancel">{{ t("common.cancel") }}</n-button>
        <n-button type="primary" @click="handleConfirm">
          {{ t("serviceReview.common.save") }}
          {{ t("serviceReview.common.submit") }}
        </n-button>
      </n-space>
    </template>
src/views/service/review/personnel/index.vue
@@ -1,7 +1,7 @@
<script setup lang="tsx">
import { onMounted, ref } from 'vue'
import type { DataTableColumns } from 'naive-ui'
import { NAvatar, NButton, NImage, NSpace, NTag, NTooltip } from 'naive-ui'
import { NButton, NImage, NSpace, NTag, NTooltip } from 'naive-ui'
import { useBoolean } from '@/hooks'
import {
  auditQualification,
@@ -60,7 +60,7 @@
    resizable: true,
  },
  {
    title: t('serviceReview.personnel.name'),
    title: t('serviceReview.personnel.searchName'),
    key: 'nickName',
    width: 150,
    resizable: true,
@@ -68,11 +68,9 @@
      return (
        <div class="flex items-center gap-2">
          {row.avatar && <img src={JSON.parse(row.avatar).url} alt="头像" class="w-24px h-24px rounded-full" />}
          {!row.avatar && (
            <div class="w-24px h-24px rounded-full flex justify-center" style="background-color: #F6F7FA;">
              <img src="/src/assets/public/avater.png" class="w-17px h-21px" />
            </div>
          )}
          {!row.avatar && <div class="w-24px h-24px rounded-full flex justify-center" style="background-color: #F6F7FA;">
            <img src="/src/assets/public/avater.png" class="w-17px h-21px" />
            </div>}
          <NTooltip trigger="hover" placement="top" max-width="500px">
            {{
              trigger: () => <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap">{row.nickName}</div>,
@@ -86,7 +84,7 @@
  {
    title: t('serviceReview.personnel.id'),
    key: 'operationsUserId',
    width: 100,
    width: 60,
    resizable: true,
  },
  {
@@ -98,7 +96,7 @@
  {
    title: t('serviceReview.personnel.role'),
    key: 'roleNames',
    width: 300,
    width: 120,
    resizable: true,
    render: (row) => {
      return (
@@ -131,6 +129,7 @@
              src={idCardFront}
              objectFit="cover"
              previewSrc={idCardFront}
              fallbackSrc="/src/assets/public/placeholder.png"
            />
            <NImage
              width={60}
@@ -138,6 +137,7 @@
              src={idCardBack}
              objectFit="cover"
              previewSrc={idCardBack}
              fallbackSrc="/src/assets/public/placeholder.png"
            />
          </div>
        )
@@ -160,6 +160,7 @@
            src={driverLicense}
            objectFit="cover"
            previewSrc={driverLicense}
            fallbackSrc="/src/assets/public/placeholder.png"
          />
        )
      }
@@ -183,6 +184,7 @@
            src={certificateOfNoCriminalRecord}
            objectFit="cover"
            previewSrc={certificateOfNoCriminalRecord}
            fallbackSrc="/src/assets/public/placeholder.png"
          />
        )
      }
@@ -206,6 +208,7 @@
            src={trainingGraduationCertificate}
            objectFit="cover"
            previewSrc={trainingGraduationCertificate}
            fallbackSrc="/src/assets/public/placeholder.png"
          />
        )
      }
@@ -215,7 +218,7 @@
  {
    title: t('serviceReview.personnel.status'),
    key: 'auditStatus',
    width: 120,
    width: 100,
    resizable: true,
    render: (row) => {
      const statusText = auditStatusLabelMap[row.auditStatus]
@@ -309,6 +312,7 @@
    content: t('serviceReview.common.deletePersonnelContent'),
    positiveText: t('common.confirm'),
    negativeText: t('common.cancel'),
    positiveButtonProps: { type: "primary" },
    onPositiveClick: async () => {
      const res = await deleteQualificationAuditById(id)
      if (res.code === 200) {
@@ -444,22 +448,39 @@
      </n-form>
    </div>
    <n-card class="flex-1 robotaxi-table-card">
    <n-card
      class="flex-1 robotaxi-table-card"
      content-style="display: flex; flex-direction: column; flex: 1; min-height: 0; min-width: 0;"
    >
      <template #header>
        <n-flex v-permission="80111">
          <NButton strong @click="handleBatchReview">
            {{ t("serviceReview.common.batchReview") }}
          </NButton>
          <n-tooltip trigger="hover" :disabled="selectedIds.length > 0">
            <template #trigger>
              <NButton
                strong
                @click="handleBatchReview"
                :disabled="selectedIds.length === 0"
              >
                {{ t("serviceReview.common.batchReview") }}
              </NButton>
            </template>
            勾选后可操作
          </n-tooltip>
        </n-flex>
      </template>
      <div class="flex-1 flex flex-col h-full">
      <div
        class="flex-1 flex flex-col h-full"
        style="min-width: 0; overflow: hidden"
      >
        <NDataTable
          class="flex-1"
          flex-height
          :columns="columns"
          :data="tableData"
          :loading="loading"
          :scroll-x="1470"
          :bordered="false"
          :row-key="(row: any) => row.id"
          @update:checked-row-keys="handleCheck"
        />
src/views/service/review/policyApplication/index.vue
@@ -183,7 +183,7 @@
    fixed: "right",
    render: (row) => {
      return (
        <div class="flex items-center justify-center gap-2">
        <div class="flex items-center gap-2">
          {row.auditStatus === 1 ? (
            <NButton
              v-permission="80122"
@@ -238,6 +238,7 @@
    content: t("serviceReview.common.deletePolicyContent"),
    positiveText: t("common.confirm"),
    negativeText: t("common.cancel"),
    positiveButtonProps: { type: "primary" },
    onPositiveClick: async () => {
      const res = await deletePolicyApplicationById(id);
      if (res.code === 200) {
@@ -360,23 +361,39 @@
      </n-form>
    </div>
    <n-card class="flex-1 robotaxi-table-card">
    <n-card
      class="flex-1 robotaxi-table-card"
      content-style="display: flex; flex-direction: column; flex: 1; min-height: 0; min-width: 0;"
    >
      <template #header>
        <n-flex v-permission="80111">
          <NButton strong @click="handleBatchReview">
            {{t("serviceReview.common.batchReview")}}
          </NButton>
          <n-tooltip trigger="hover" :disabled="selectedIds.length > 0">
            <template #trigger>
              <NButton
                strong
                @click="handleBatchReview"
                :disabled="selectedIds.length === 0"
              >
                {{ t("serviceReview.common.batchReview") }}
              </NButton>
            </template>
            勾选后可操作
          </n-tooltip>
        </n-flex>
      </template>
      <div class="flex-1 flex flex-col h-full">
      <div
        class="flex-1 flex flex-col h-full"
        style="min-width: 0; overflow: hidden"
      >
        <n-data-table
          class="flex-1"
          flex-height
          :columns="columns"
          :data="tableData"
          :loading="loading"
          :scroll-x="1500"
          :scroll-x="1470"
          :bordered="false"
          :row-key="(row: any) => row.id"
          @update:checked-row-keys="handleCheck"
        />
src/views/service/ticket/feedback/index.vue
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref, h, onMounted } from "vue";
<script setup lang="tsx">
import { ref, h, onMounted, computed } from "vue";
import { useI18n } from "vue-i18n";
import type { DataTableColumns } from "naive-ui";
import {
  NButton,
@@ -7,9 +8,13 @@
  NImageGroup,
  NModal,
  NSpace,
  NAvatar,
  NTooltip,
} from "naive-ui";
import { useBoolean } from "@/hooks";
const { t } = useI18n();
import { fetchFeedbackPageList } from "@/service/api/serviceCenter";
const {
@@ -66,12 +71,12 @@
  return normalizeImages(row.feedbackPictures);
}
const columns: DataTableColumns<FeedbackItem> = [
const columns = computed<DataTableColumns<any>>(() => [
  {
    title: "序号",
    title: t("ticket.feedback.index"),
    key: "index",
    width: 80,
    align: "center",
    resizable: true,
    render: (_row, index) =>
      h(
        "span",
@@ -79,44 +84,46 @@
      ),
  },
  {
    title: "反馈人",
    title: t("ticket.feedback.feedbackPerson"),
    key: "nickName",
    width: 120,
    align: "center",
    resizable: true,
    render: (row) => {
      const displayName = row.nickName || "-";
      return h("div", { class: "flex items-center justify-center gap-2" }, [
        h(
          NAvatar,
          {
            round: true,
            size: 32,
            style: { backgroundColor: "#e0e0e0" },
            src: row.avatar || undefined,
          },
          { default: () => displayName.charAt(0) }
        ),
        h("span", displayName),
      ]);
      return (
        <div class="flex items-center gap-2">
          {row.avatar && <img src={JSON.parse(row.avatar).url} alt="头像" class="w-24px h-24px rounded-full" />}
          {!row.avatar && <div class="w-24px h-24px rounded-full flex justify-center" style="background-color: #F6F7FA;">
            <img src="/src/assets/public/avater.png" class="w-17px h-21px" />
            </div>}
          <NTooltip trigger="hover" placement="top" max-width="500px">
            {{
              trigger: () => <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap">{row.nickName}</div>,
              default: () => row.nickName,
            }}
          </NTooltip>
        </div>
      )
    },
  },
  {
    title: "反馈类型",
    title: t("ticket.feedback.feedbackType"),
    key: "feedbackTypeName",
    width: 120,
    align: "center",
    width: 140,
    resizable: true,
    render: (row) => row.feedbackTypeName || "-",
  },
  {
    title: "反馈内容",
    title: t("ticket.feedback.feedbackContent"),
    key: "feedbackContent",
    minWidth: 200,
    align: "center",
    minWidth: 600,
    resizable: true,
    render: (row) => row.feedbackContent || "-",
  },
  {
    title: "图片",
    title: t("ticket.feedback.image"),
    key: "feedbackPictures",
    width: 120,
    align: "center",
    width: 70,
    resizable: true,
    render: (row) => {
      const images = getImages(row);
      if (!images.length) return h("span", "-");
@@ -127,23 +134,25 @@
          type: "primary",
          onClick: () => handleViewImages(images),
        },
        { default: () => "查看" }
        { default: () => t("ticket.feedback.view") }
      );
    },
  },
  {
    title: "联系方式",
    title: t("ticket.feedback.contact"),
    key: "phoneNumber",
    width: 140,
    align: "center",
    resizable: true,
    render: (row) => row.phoneNumber || "-",
  },
  {
    title: "反馈时间",
    title: t("ticket.feedback.feedbackTime"),
    key: "createTime",
    width: 160,
    align: "center",
    resizable: true,
    render: (row) => row.createTime || "-",
  },
];
]);
const handleSearch = () => {
  pagination.value.page = 1;
@@ -190,68 +199,75 @@
onMounted(() => {
  getFeedbackList();
});
const filterItems = computed(() => [
  {
    label: t('ticket.feedback.feedbackContent'),
    path: 'feedbackContent',
    render: () => <n-input v-model:value={searchForm.value.feedbackContent} placeholder={t('common.inputPlaceholder')} />,
  }
])
</script>
<template>
  <div class="h-full flex flex-col">
    <n-card style="border-radius: 6px" content-style="padding: 12px">
  <div class="roboTaxi">
    <div ref="formRef" class="roboTaxi-form-card mb-12px">
      <n-form
        :model="searchForm"
        label-placement="left"
        inline
        :show-feedback="false"
      >
        <div class="flex justify-between items-center w-full">
          <div class="flex gap-4">
            <n-form-item label="反馈内容">
              <n-input
                v-model:value="searchForm.feedbackContent"
                placeholder="请输入"
                clearable
                style="width: 200px"
              />
        <SearchPanel>
          <n-gi v-for="item in filterItems" :key="item.path">
            <n-form-item :label="item.label" :path="item.path">
              <component :is="item.render" />
            </n-form-item>
          </div>
          <div class="flex gap-2">
            <n-button style="width: 84px; height: 32px" @click="handleReset"
              >重置</n-button
            >
            <n-button
              type="primary"
              style="width: 84px; height: 32px"
              @click="handleSearch"
              >查询</n-button
            >
          </div>
        </div>
          </n-gi>
          <template #action>
            <NButton strong @click="handleReset">
              {{ $t("common.reset") }}
            </NButton>
            <NButton type="primary" @click="handleSearch">
              {{ $t("common.search") }}
            </NButton>
          </template>
        </SearchPanel>
      </n-form>
    </n-card>
    </div>
    <n-card
      style="border-radius: 6px; margin-top: 12px"
      content-style="padding: 12px"
      class="flex-1 robotaxi-table-card"
      content-style="display: flex; flex-direction: column; flex: 1; min-height: 0; min-width: 0;"
    >
      <NSpace vertical>
      <div
        class="flex-1 flex flex-col h-full"
        style="min-width: 0; overflow: hidden"
      >
        <n-data-table
          class="flex-1"
          flex-height
          :columns="columns"
          :data="tableData"
          :loading="loading"
          :scroll-x="1200"
          :row-key="(row: FeedbackItem) => row.id"
          :scroll-x="1410"
          :bordered="false"
          :row-key="(row: any) => row.id"
        />
        <Pagination
          :count="count"
          :page="pagination.page"
          :page-size="pagination.pageSize"
          class="robotaxi-pagination mt-12px"
          @change="changePage"
        />
      </NSpace>
      </div>
    </n-card>
    <n-modal
      v-model:show="previewVisible"
      preset="card"
      title="图片预览"
      :title="$t('ticket.feedback.imagePreview')"
      :style="{ width: '720px' }"
      :bordered="false"
      :segmented="{ content: true, action: true }"
src/views/service/ticket/feedbackType/index.vue
@@ -1,8 +1,12 @@
<script setup lang="tsx">
import { ref, h, computed, onMounted } from "vue";
import { useI18n } from "vue-i18n";
import type { DataTableColumns, FormInst, FormRules } from "naive-ui";
import { NButton } from "naive-ui";
import { useBoolean } from "@/hooks";
const { t } = useI18n();
import {
  fetchFeedbackTypeList,
  saveFeedbackType,
@@ -29,64 +33,62 @@
  feedbackType: "",
});
const rules: FormRules = {
  feedbackType: [{ required: true, message: "请输入反馈类型", trigger: "blur" }],
};
const rules = computed<FormRules>(() => ({
  feedbackType: [{ required: true, message: t("ticket.feedbackType.required"), trigger: "blur" }],
}));
const modalTitle = computed(() =>
  modalMode.value === "add" ? "新增" : "编辑"
  modalMode.value === "add" ? t("ticket.feedbackType.create") : t("ticket.feedbackType.edit")
);
const columns: DataTableColumns<any> = [
const columns = computed<DataTableColumns<any>>(() => [
  {
    title: "序号",
    title: t("ticket.feedbackType.index"),
    key: "index",
    width: 80,
    align: "center",
    width: 100,
    resizable: true,
    render: (_row, index) => h("span", index + 1),
  },
  {
    title: "反馈类型",
    title: t("ticket.feedbackType.feedbackType"),
    key: "feedbackType",
    minWidth: 200,
    align: "center",
    resizable: true,
  },
  {
    title: "创建时间",
    title: t("ticket.feedbackType.createTime"),
    key: "createTime",
    width: 180,
    align: "center",
    width: 350,
    resizable: true,
    render: (row) => row.createTime || "-",
  },
  {
    title: "操作",
    title: t("ticket.feedbackType.actions"),
    key: "actions",
    width: 160,
    align: "center",
    width: 100,
    render: (row) => {
      return (
        <div class="flex items-center justify-center gap-2">
        <div class="flex items-center gap-2">
          <NButton
            v-permission="80232"
            text
            type="primary"
            onClick={() => handleEdit(row)}
          >
            编辑
            {t("ticket.feedbackType.edit")}
          </NButton>
          <NButton
            v-permission="8023"
            text
            type="error"
            type="primary"
            onClick={() => handleDelete(row)}
          >
            删除
            {t("ticket.feedbackType.delete")}
          </NButton>
        </div>
      );
    },
  },
];
]);
function handleAdd() {
  modalMode.value = "add";
@@ -112,11 +114,11 @@
  if (modalMode.value === "add") {
    const res = await saveFeedbackType(payload);
    if (res.code === 200) {
      window.$message?.success("新增成功");
      window.$message?.success(t("ticket.feedbackType.addSuccess"));
      modalVisible.value = false;
      getFeedbackTypeList();
    } else {
      window.$message?.error("新增失败");
      window.$message?.error(t("ticket.feedbackType.addFail"));
    }
    return;
  }
@@ -125,27 +127,28 @@
    ...payload,
  });
  if (res.code === 200) {
    window.$message?.success("编辑成功");
    window.$message?.success(t("ticket.feedbackType.editSuccess"));
    modalVisible.value = false;
    getFeedbackTypeList();
  } else {
    window.$message?.error("编辑失败");
    window.$message?.error(t("ticket.feedbackType.editFail"));
  }
}
function handleDelete(row: FeedbackTypeItem) {
  window.$dialog?.warning({
    title: "确认删除",
    content: "确定要删除该反馈类型吗?",
    positiveText: "确定",
    negativeText: "取消",
    title: t("ticket.feedbackType.confirmDelete"),
    content: t("ticket.feedbackType.confirmDeleteContent"),
    positiveText: t("common.confirm"),
    negativeText: t("common.cancel"),
    positiveButtonProps: { type: "primary" },
    onPositiveClick: async () => {
      const res = await deleteFeedbackType(row.id);
      if (res.code === 200) {
        window.$message?.success("删除成功");
        window.$message?.success(t("ticket.feedbackType.deleteSuccess"));
        getFeedbackTypeList();
      } else {
        window.$message?.error("删除失败");
        window.$message?.error(t("ticket.feedbackType.deleteFail"));
      }
    },
  });
@@ -174,37 +177,31 @@
onMounted(() => {
  getFeedbackTypeList();
});
</script>
<template>
  <div class="h-full flex flex-col">
    <n-card
      style="border-radius: 6px; margin-top: 12px"
      content-style="padding: 12px"
    >
      <div class="mb-4 flex justify-end gap-2">
        <n-button
          v-permission="80221"
          style="
            width: 84px;
            height: 32px;
            color: #0068ff;
            background-color: #e6f4ff;
            border: 1px solid #7abdff;
          "
          @click="handleAdd"
          >新增</n-button
        >
      </div>
  <div class="roboTaxi">
    <n-card class="flex-1">
      <template #header>
        <n-flex justify="flex-end" v-permission="80221">
          <n-button type="primary" secondary @click="handleAdd">{{ $t("ticket.feedbackType.add") }}</n-button>
        </n-flex>
      </template>
      <n-data-table
        :columns="columns"
        :data="tableData"
        :loading="loading"
        :pagination="false"
        :scroll-x="800"
        :row-key="(row: FeedbackTypeItem) => row.id"
      />
      <div
        class="flex-1 flex flex-col h-full"
        style="min-width: 0; overflow: hidden"
      >
        <n-data-table
          :columns="columns"
          :data="tableData"
          :loading="loading"
          :pagination="false"
          :bordered="false"
          :row-key="(row: FeedbackTypeItem) => row.id"
        />
      </div>
    </n-card>
    <n-modal
@@ -219,14 +216,14 @@
        ref="formRef"
        :model="formModel"
        :rules="rules"
        label-placement="left"
        label-placement="top"
        label-width="80"
        require-mark-placement="left"
      >
        <n-form-item label="反馈类型" path="feedbackType">
        <n-form-item :label="$t('ticket.feedbackType.feedbackType')" path="feedbackType">
          <n-input
            v-model:value="formModel.feedbackType"
            placeholder="请输入5个字以内"
            :placeholder="$t('ticket.feedbackType.inputPlaceholder')"
            clearable
            maxlength="5"
          />
@@ -235,8 +232,8 @@
      <template #action>
        <n-space justify="end">
          <n-button @click="handleClose">取消</n-button>
          <n-button type="primary" @click="handleSave">保存</n-button>
          <n-button @click="handleClose">{{ $t("common.cancel") }}</n-button>
          <n-button type="primary" @click="handleSave">{{ $t("common.confirm") }}</n-button>
        </n-space>
      </template>
    </n-modal>