Просмотр исходного кода

Merge branch 'all-v4-lkh' into 'all-v4'

All v4 lkh

See merge request skyeye/skyeye_frontend/skyeye-admin!373
Fei Liu 1 год назад
Родитель
Сommit
8f647bda18

+ 30 - 0
src/api/comments/comments.ts

@@ -0,0 +1,30 @@
+import { http } from '@/utils/http/axios';
+import { COMMENTSTATUS } from '@/types/comments/constant';
+import { CommentsQuery, ListType } from '@/types/comments/type';
+
+//查询留言列表
+export const getCommentsList = (data: CommentsQuery) => {
+  return http.request<ListType>({
+    url: `/admin/comment/queryCommentPageByFilters`,
+    method: 'post',
+    data,
+  });
+};
+
+//更新评论状态
+export const undateCommentStatus = (data: { id: number; status: COMMENTSTATUS }) => {
+  return http.request({
+    url: `/admin/comment/updateCommentStatus`,
+    method: 'PUT',
+    data,
+  });
+};
+
+//回复评论
+export const replyComment = (data: { id: number; reply: string }) => {
+  return http.request({
+    url: `/admin/comment/replyComment`,
+    method: 'post',
+    data,
+  });
+};

+ 10 - 0
src/router/full-routes.ts

@@ -468,6 +468,16 @@ const fullRoutes: AppRouteRecordRaw[] = [
           title: '权限管理',
           title: '权限管理',
         },
         },
       },
       },
+      {
+        // 权限管理
+        path: 'comments',
+        name: 'SystemComments',
+        component: '/system/comments/PageCommentsManage',
+        meta: {
+          icon: '',
+          title: '评论管理',
+        },
+      },
       {
       {
         // 意见反馈
         // 意见反馈
         path: 'feedback',
         path: 'feedback',

+ 12 - 0
src/types/comments/constant.ts

@@ -0,0 +1,12 @@
+export enum REPLYSTATUS {
+  replied = 1,
+  unReplied = 0,
+  all = 2,
+}
+
+export enum COMMENTSTATUS {
+  unAuthed = 0,
+  rejected = 1,
+  passed = 2,
+  all = 3,
+}

+ 41 - 0
src/types/comments/type.ts

@@ -0,0 +1,41 @@
+import { REPLYSTATUS, COMMENTSTATUS } from '@/types/comments/constant';
+
+export interface Records {
+  id: number; //评论id
+  userId: number; //用户id
+  userName: string; //用户名
+  avatar: string; //用户头像
+  comment: string; //评论内容
+  picUrl: string; //评论图片
+  staffNo: string; //员工编号
+  mobile: string; //手机号
+  // problemImage: Array<File>;
+  reply: string | null; //回复内容
+  isReplied: REPLYSTATUS; //是否已回复
+  replyAt: string | null; //回复时间
+  status: COMMENTSTATUS; //审核状态
+  createdAt: string; //创建时间
+  updatedAt: string; //更新时间
+  isUserDeleted: number;
+  tenantId: number; //租户id
+  locationName: string;
+}
+
+export interface ListType {
+  records: Records[];
+  maxPageSize?: number;
+  pageNumber: number;
+  pageSize: number;
+  totalPage: number;
+  totalRow: number;
+  optimizeCountQuery: boolean;
+}
+
+export interface CommentsQuery {
+  pageNumber: number; //评论id
+  pageSize: number; //用户id
+  queryParam?: {
+    isReplied?: number;
+    isApproved?: number;
+  };
+}

+ 101 - 0
src/views/system/comments/PageCommentsManage.vue

@@ -0,0 +1,101 @@
+<template>
+  <div class="feedback-page">
+    <div class="feedback-head">
+      <div class="search-input">
+        <el-form :inline="true" :model="listFilter" class="demo-form-inline">
+          <el-form-item label="审核状态:">
+            <el-select v-model="listFilter.authStatus" placeholder="请选择审核状态" @change="getList">
+              <el-option label="全部" :value="COMMENTSTATUS.all" />
+              <el-option label="已通过" :value="COMMENTSTATUS.passed" />
+              <el-option label="未通过" :value="COMMENTSTATUS.rejected" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="回复状态:">
+            <el-select v-model="listFilter.replyStatus" placeholder="请选择审核状态" @change="getList">
+              <el-option label="全部" :value="REPLYSTATUS.all" />
+              <el-option label="已回复" :value="REPLYSTATUS.replied" />
+              <el-option label="未回复" :value="REPLYSTATUS.unReplied" />
+            </el-select>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+    <div class="problem-list"
+      ><SingleComment
+        v-for="(item, index) in commentsList"
+        :key="index"
+        :problem-data="item"
+        @reFreshList="getList"
+        style="margin-top: 22px; margin-bottom: 2px"
+    /></div>
+
+    <el-pagination
+      v-model:current-page="pageNumber"
+      v-model:page-size="pageSize"
+      background
+      layout="prev, pager, next,sizes,jumper"
+      :page-sizes="[5, 10, 20, 40]"
+      :total="totalRow"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+      class="flex justify-end"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import SingleComment from './component/SingleComment.vue';
+  import useComments from './use-comments.ts';
+  import { REPLYSTATUS, COMMENTSTATUS } from '@/types/comments/constant.ts';
+
+  const useCommentsList = useComments();
+  const { commentsList, pageNumber, pageSize, totalRow, getList, listFilter } = useCommentsList;
+
+  const handleSizeChange = () => {
+    pageNumber.value = 1;
+    getList();
+  };
+  const handleCurrentChange = () => {
+    getList();
+  };
+</script>
+
+<style scoped>
+  .feedback-page {
+    position: relative;
+    height: calc(100vh - 64px - 12px);
+    background-color: #ffffff;
+    padding-top: 20px;
+    padding-left: 20px;
+    padding-right: 20px;
+  }
+
+  :deep(.el-radio-button .el-radio-button__inner) {
+    width: 188px;
+    background-color: #00000005;
+  }
+
+  .feedback-head {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .search-input {
+    margin-top: 24px;
+  }
+
+  .problem-list {
+    height: calc(100vh - 270px);
+    overflow: auto;
+    margin-bottom: 4px;
+    padding: 0 2px;
+  }
+
+  :deep(.el-pagination .el-select) {
+    width: 92px;
+  }
+  .demo-form-inline .el-select {
+    --el-select-width: 220px;
+  }
+</style>
+./use-comments.ts @/types/comments/constant.ts

+ 211 - 0
src/views/system/comments/component/SingleComment.vue

@@ -0,0 +1,211 @@
+<template>
+  <div
+    class="single-item"
+    :style="`padding-bottom:${props.problemData.isReplied === REPLYSTATUS.replied ? '16px' : '38px'}`"
+  >
+    <div style="display: flex; font-size: 12px">
+      <div style="color: #00000073">评论人:{{ props.problemData.userName }}-{{ props.problemData.staffNo }} </div>
+      <div style="margin-left: 20px; color: #00000073">日期:{{ props.problemData.createdAt }}</div>
+      <!-- <img src="@/assets/icons/phone.png" alt="" /> -->
+      <div class="single-contact">联系方式:{{ props.problemData.mobile }}</div>
+    </div>
+    <div class="buttonBar">
+      <el-button
+        v-show="props.problemData.status === COMMENTSTATUS.passed"
+        type="primary"
+        style="width: 74px; margin-left: auto; margin-right: 0"
+        @click="hideComment"
+        >隐藏</el-button
+      >
+      <el-button
+        v-show="props.problemData.status === COMMENTSTATUS.rejected"
+        type="primary"
+        style="width: 74px; margin-left: auto; margin-right: 0"
+        @click="displayComment"
+        >显示</el-button
+      >
+      <el-button
+        v-show="
+          props.problemData.isReplied === REPLYSTATUS.unReplied && props.problemData.status === COMMENTSTATUS.passed
+        "
+        style="width: 74px"
+        type="primary"
+        @click="openReply = true"
+        >回复</el-button
+      >
+      <el-button v-show="props.problemData.isReplied === REPLYSTATUS.replied" style="width: 74px" disabled
+        >已回复</el-button
+      >
+    </div>
+
+    <el-divider />
+    <div class="problem-describe">
+      <div>评论内容:</div>
+      <div class="problem-content">{{ props.problemData.comment }}</div>
+      <div v-show="props.problemData.isUserDeleted === 1" class="delete-label"></div>
+    </div>
+    <div class="problem-picture">
+      <div class="picture-content" v-for="(item, index) in problemImageUrls" :key="index">
+        <el-image
+          style="width: 80px; height: 80px"
+          :src="item"
+          :zoom-rate="1.2"
+          :max-scale="7"
+          :min-scale="0.2"
+          :preview-src-list="problemImageUrls"
+          :initial-index="index"
+          fit="cover"
+        />
+      </div>
+    </div>
+
+    <div v-if="openReply === true" style="position: relative">
+      <el-input placeholder="请输入回复(不超过30个字符)" type="textarea" v-model="replyContent" rows="4"> </el-input>
+      <span
+        style="
+          position: absolute;
+          left: 10px;
+          padding-bottom: 5px;
+          bottom: 2px;
+          color: grey;
+          background-color: #fff;
+          line-height: 0;
+          font-size: 12px;
+        "
+      >
+        <span style="line-height: normal" :style="replyContent.length > 203 ? 'color:red' : ''">{{
+          replyContent.length - 3
+        }}</span>
+        <span style="line-height: normal">/200</span>
+      </span>
+      <div style="position: absolute; height: 32px; right: 10px; padding-bottom: 5px; bottom: 2px">
+        <el-button style="margin-top: 3px" type="primary" size="small" @click="submitReply">发布</el-button>
+        <el-button style="margin-top: 3px" size="small" @click="openReply = false">取消</el-button>
+      </div>
+    </div>
+
+    <div v-if="props.problemData.isReplied === REPLYSTATUS.replied" style="position: relative">
+      <el-input type="textarea" v-model="props.problemData.reply" rows="3" disabled> </el-input>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, computed } from 'vue';
+  import { undateCommentStatus, replyComment } from '@/api/comments/comments';
+  import { REPLYSTATUS, COMMENTSTATUS } from '@/types/comments/constant';
+  import { Records } from '@/types/comments/type';
+  const props = defineProps<{
+    problemData: Records;
+  }>();
+
+  const emit = defineEmits(['reFreshList']);
+
+  const problemImageUrls = computed(() => {
+    const imageUrlString = props.problemData.picUrl;
+    return imageUrlString ? imageUrlString.split(',') : [];
+  });
+
+  const hideComment = () => {
+    undateCommentStatus({ id: props.problemData.id, status: COMMENTSTATUS.rejected }).then(() => {
+      emit('reFreshList');
+    });
+  };
+  const displayComment = () => {
+    undateCommentStatus({ id: props.problemData.id, status: COMMENTSTATUS.passed }).then(() => {
+      emit('reFreshList');
+    });
+  };
+  const submitReply = () => {
+    replyComment({ id: props.problemData.id, reply: replyContent.value }).then(() => {
+      openReply.value = false;
+      emit('reFreshList');
+    });
+  };
+
+  const openReply = ref(false);
+
+  const replyContent = ref('回复:');
+</script>
+
+<style scoped>
+  .single-item {
+    background: #ffffff;
+    box-shadow: 0px 1px 8px 0px rgba(0, 0, 0, 0.09);
+    border-radius: 6px;
+    padding: 16px 12px 0px 12px;
+    position: relative;
+  }
+
+  .single-contact {
+    margin-left: 20px;
+    margin-right: 40px;
+    color: #00000073;
+  }
+  .buttonBar {
+    position: absolute;
+    display: flex;
+    width: 200px;
+    top: 11px;
+    right: 12px;
+  }
+  .single-handle {
+    position: relative;
+    top: 11px;
+    right: 12px;
+  }
+
+  .el-divider--horizontal {
+    margin: 16px 0;
+  }
+
+  .problem-type,
+  .problem-describe {
+    font-size: 14px;
+    white-space: nowrap;
+    word-break: break-word;
+    display: flex;
+    margin-bottom: 8px;
+    .delete-label {
+      width: 100px;
+      height: 60px;
+      position: absolute;
+      right: 80px;
+      top: 60px;
+      background-image: url('@/assets/icons/config-fail.png');
+      background-size: 100% 100%;
+      cursor: pointer;
+
+      background-size: 100% 100%;
+    }
+  }
+
+  .type-content,
+  .problem-content {
+    white-space: pre-wrap;
+    word-break: break-word;
+  }
+  .type-content {
+    font-weight: 600;
+  }
+  .problem-picture {
+    display: flex;
+    gap: 20px;
+  }
+
+  :deep(.el-collapse-item__header) {
+    border-bottom: none;
+  }
+
+  :deep(.el-collapse-item__wrap) {
+    border-bottom: none;
+  }
+
+  :deep(.el-collapse-item__content) {
+    padding-bottom: 0px;
+  }
+  :deep(.el-collapse) {
+    border-top: none;
+  }
+</style>
+@/types/comments/constant

+ 56 - 0
src/views/system/comments/use-comments.ts

@@ -0,0 +1,56 @@
+import { getCommentsList } from '@/api/comments/comments';
+import { REPLYSTATUS, COMMENTSTATUS } from '@/types/comments/constant';
+import { Records, CommentsQuery } from '@/types/comments/type';
+import { onMounted, ref } from 'vue';
+
+export function useCommentsList() {
+  const commentsList = ref<Records[]>([]);
+  const pageNumber = ref<number>(1);
+  const pageSize = ref<number>(10);
+  const totalPage = ref<number>();
+  const totalRow = ref<number>();
+
+  const listFilter = ref({
+    authStatus: COMMENTSTATUS.all,
+    replyStatus: REPLYSTATUS.all,
+  });
+
+  const getList = () => {
+    const params: CommentsQuery = {
+      pageNumber: pageNumber.value,
+      pageSize: pageSize.value,
+    };
+    if (listFilter.value.authStatus !== COMMENTSTATUS.all) {
+      //若传1,则拉取审核通过的;若传0,则拉取审核未通过的和未审核的
+      params.queryParam = {
+        isApproved: listFilter.value.authStatus === COMMENTSTATUS.passed ? 1 : 0,
+      };
+    }
+    if (listFilter.value.replyStatus !== REPLYSTATUS.all) {
+      params.queryParam = {
+        isReplied: listFilter.value.replyStatus,
+      };
+    }
+    getCommentsList(params).then((res) => {
+      commentsList.value = res.records;
+      totalPage.value = res.totalPage;
+      totalRow.value = res.totalRow;
+    });
+  };
+
+  onMounted(() => {
+    getList();
+  });
+
+  return {
+    commentsList,
+    pageNumber,
+    pageSize,
+    totalPage,
+    totalRow,
+    getList,
+    listFilter,
+  };
+}
+
+export default useCommentsList;