Commit 81906bdb60ce220da9894a7faea8b550bd5e12ac

Authored by xp.Huang
2 parents 93cb8a66 b3e18f19

Merge branch 'f-dev' into 'main'

feat:新增意见反馈功能

See merge request huang/yun-teng-iot-front!203
  1 +import { defHttp } from '/@/utils/http/axios';
  2 +import { CameraModel, CameraQueryParam } from './model/feedbackModel';
  3 +
  4 +enum CameraManagerApi {
  5 + CAMERA_POST_URL = '/opinion',
  6 + CAMERA_GET_URL = '/opinion',
  7 + CAMERA_DELETE_URL = '/opinion',
  8 + CAMERA_GET_DETAIL_URL = '/opinion',
  9 +}
  10 +
  11 +export const feedbackPage = (params: CameraQueryParam) => {
  12 + return defHttp.get<CameraQueryParam>({
  13 + url: CameraManagerApi.CAMERA_GET_URL,
  14 + params,
  15 + });
  16 +};
  17 +
  18 +/**
  19 + * 删除视频
  20 + * @param ids 删除的ids
  21 + */
  22 +export const deleteFeedbackManage = (ids: string[]) => {
  23 + return defHttp.delete({
  24 + url: CameraManagerApi.CAMERA_DELETE_URL,
  25 + data: {
  26 + ids: ids,
  27 + },
  28 + });
  29 +};
  30 +
  31 +// 创建或编辑视频
  32 +export const createOrEditFeedBackManage = (data) => {
  33 + return defHttp.post<CameraModel>({
  34 + url: CameraManagerApi.CAMERA_POST_URL,
  35 + data,
  36 + });
  37 +};
  38 +
  39 +// 查询视频详情
  40 +export const getCameraManageDetail = (id: string) => {
  41 + return defHttp.get({
  42 + url: CameraManagerApi.CAMERA_GET_DETAIL_URL + `/${id}`,
  43 + });
  44 +};
... ...
  1 +import { BasicPageParams } from '/@/api/model/baseModel';
  2 +export type CameraQueryParam = BasicPageParams & CameraParam;
  3 +
  4 +export type CameraParam = {
  5 + status?: true;
  6 + name?: string;
  7 + organizationId?: string;
  8 + orderFiled?: string;
  9 + orderType?: string;
  10 +};
  11 +
  12 +export interface CameraModel {
  13 + category?: string;
  14 + contact?: string;
  15 + createTime?: string;
  16 + creator?: string;
  17 + defaultConfig?: string;
  18 + description?: string;
  19 + enabled?: true;
  20 + icon?: string;
  21 + id?: string;
  22 + images?: string;
  23 + message?: string;
  24 + name?: string;
  25 + remark?: string;
  26 + roleIds?: [string];
  27 + status?: string;
  28 + tenantExpireTime?: string;
  29 + tenantId?: string;
  30 + tenantProfileId?: string;
  31 + tenantStatus?: string;
  32 + title?: string;
  33 + updateTime?: string;
  34 + updater?: string;
  35 +}
... ...
  1 +<template>
  2 + <BasicDrawer v-bind="$attrs" @register="registerDrawer" title="意见反馈预览" width="30%">
  3 + <BasicForm @register="registerForm">
  4 + <template #iconSelect>
  5 + <div style="width: 22vw; display: flex; flex-wrap: wrap">
  6 + <template v-for="(item, index) in feedBackImgUrl" :key="index">
  7 + <span style="display: none">{{ index }}</span>
  8 + <div>
  9 + <Upload list-type="picture-card" :openFileDialogOnClick="false">
  10 + <img @click="handlePreview(item)" :src="item" alt="avatar" />
  11 + </Upload>
  12 + </div>
  13 + </template>
  14 + <Modal
  15 + :visible="previewVisible"
  16 + :title="previewTitle"
  17 + :footer="null"
  18 + @cancel="handleCancel"
  19 + >
  20 + <img alt="example" style="width: 100%" :src="previewImage" />
  21 + </Modal>
  22 + </div>
  23 + </template>
  24 + </BasicForm>
  25 + </BasicDrawer>
  26 +</template>
  27 +<script lang="ts">
  28 + import { defineComponent, ref } from 'vue';
  29 + import { BasicForm, useForm } from '/@/components/Form';
  30 + import { formSchema } from './config.data';
  31 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  32 + import { Upload } from 'ant-design-vue';
  33 + import { Modal } from 'ant-design-vue';
  34 +
  35 + export default defineComponent({
  36 + name: 'ContactDrawer',
  37 + components: { BasicDrawer, BasicForm, Upload, Modal },
  38 + emits: ['success', 'register'],
  39 + setup(_) {
  40 + const feedBackImgUrl = ref([]);
  41 + const previewVisible = ref(false);
  42 + const previewImage = ref('');
  43 + const previewTitle = ref('');
  44 + const [registerForm, { setFieldsValue, resetFields }] = useForm({
  45 + labelWidth: 120,
  46 + schemas: formSchema,
  47 + showActionButtonGroup: false,
  48 + });
  49 +
  50 + const [registerDrawer, { setDrawerProps }] = useDrawerInner(async (data) => {
  51 + await resetFields();
  52 + feedBackImgUrl.value = [];
  53 + setDrawerProps({ confirmLoading: false });
  54 + await setFieldsValue(data.record);
  55 + try {
  56 + const contactFormJson = JSON.parse(data.record.contact);
  57 + const imageFormJson = JSON.parse(data.record.images);
  58 + feedBackImgUrl.value = imageFormJson;
  59 + await setFieldsValue({
  60 + qq: contactFormJson.qq,
  61 + email: contactFormJson.email,
  62 + phone: contactFormJson.phone,
  63 + });
  64 + } catch (e) {
  65 + console.log('意见反馈', e);
  66 + }
  67 + });
  68 + const handlePreview = async (item) => {
  69 + previewVisible.value = true;
  70 + previewImage.value = item;
  71 + previewTitle.value = '预览图片';
  72 + };
  73 + const handleCancel = () => {
  74 + previewVisible.value = false;
  75 + };
  76 + return {
  77 + registerDrawer,
  78 + registerForm,
  79 + feedBackImgUrl,
  80 + handlePreview,
  81 + previewVisible,
  82 + handleCancel,
  83 + previewImage,
  84 + previewTitle,
  85 + };
  86 + },
  87 + });
  88 +</script>
... ...
  1 +import { BasicColumn, FormSchema } from '/@/components/Table';
  2 +
  3 +// 表格列数据
  4 +export const columns: BasicColumn[] = [
  5 + {
  6 + title: '主题',
  7 + dataIndex: 'title',
  8 + width: 80,
  9 + },
  10 + {
  11 + title: '姓名',
  12 + dataIndex: 'name',
  13 + width: 120,
  14 + },
  15 + {
  16 + title: '反馈信息',
  17 + dataIndex: 'message',
  18 + width: 120,
  19 + },
  20 + {
  21 + title: '反馈方式',
  22 + dataIndex: 'config',
  23 + width: 180,
  24 + slots: { customRender: 'config' },
  25 + },
  26 + {
  27 + title: '添加时间',
  28 + dataIndex: 'createTime',
  29 + width: 180,
  30 + },
  31 +];
  32 +
  33 +// 查询字段
  34 +export const searchFormSchema: FormSchema[] = [
  35 + {
  36 + field: 'name',
  37 + label: '姓名',
  38 + component: 'Input',
  39 + colProps: { span: 8 },
  40 + componentProps: {
  41 + maxLength: 36,
  42 + placeholder: '请输入姓名',
  43 + },
  44 + },
  45 +];
  46 +
  47 +// 弹框配置项
  48 +export const formSchema: FormSchema[] = [
  49 + {
  50 + field: 'title',
  51 + label: '主题',
  52 + component: 'Input',
  53 + componentProps: {
  54 + disabled: true,
  55 + },
  56 + },
  57 + {
  58 + field: 'name',
  59 + label: '姓名',
  60 + component: 'Input',
  61 + componentProps: {
  62 + disabled: true,
  63 + },
  64 + },
  65 + {
  66 + field: 'phone',
  67 + label: '手机',
  68 + component: 'Input',
  69 + componentProps: {
  70 + disabled: true,
  71 + },
  72 + },
  73 + {
  74 + field: 'qq',
  75 + label: 'QQ',
  76 + component: 'Input',
  77 + componentProps: {
  78 + disabled: true,
  79 + },
  80 + },
  81 + {
  82 + field: 'email',
  83 + label: '邮箱',
  84 + component: 'Input',
  85 + componentProps: {
  86 + disabled: true,
  87 + },
  88 + },
  89 + {
  90 + field: 'message',
  91 + label: '反馈信息',
  92 + colProps: { span: 24 },
  93 + component: 'InputTextArea',
  94 + componentProps: {
  95 + disabled: true,
  96 + },
  97 + },
  98 + {
  99 + field: 'images',
  100 + label: '预览图片',
  101 + slot: 'iconSelect',
  102 + component: 'Input',
  103 + },
  104 +];
... ...
  1 +<template>
  2 + <div>
  3 + <BasicTable :clickToRowSelect="false" @register="registerTable" :searchInfo="searchInfo">
  4 + <template #toolbar>
  5 + <a-button
  6 + type="primary"
  7 + color="error"
  8 + @click="handleDeleteOrBatchDelete(null)"
  9 + :disabled="hasBatchDelete"
  10 + >
  11 + 批量删除
  12 + </a-button>
  13 + </template>
  14 + <template #config="{ record }">
  15 + <a-button type="link" class="ml-2" @click="showData(record)"> 查看反馈方式 </a-button>
  16 + </template>
  17 + <template #action="{ record }">
  18 + <TableAction
  19 + :actions="[
  20 + {
  21 + label: '预览',
  22 + icon: 'clarity:note-edit-line',
  23 + onClick: handleViewVideo.bind(null, record),
  24 + },
  25 + {
  26 + label: '删除',
  27 + icon: 'ant-design:delete-outlined',
  28 + color: 'error',
  29 + popConfirm: {
  30 + title: '是否确认删除',
  31 + confirm: handleDeleteOrBatchDelete.bind(null, record),
  32 + },
  33 + },
  34 + ]"
  35 + />
  36 + </template>
  37 + </BasicTable>
  38 + <FeedbackDrawer @register="registerDrawer" @success="handleSuccess" />
  39 + </div>
  40 +</template>
  41 +
  42 +<script lang="ts">
  43 + import { defineComponent, reactive, ref, computed, h } from 'vue';
  44 + import { BasicTable, useTable, TableAction } from '/@/components/Table';
  45 + import { useMessage } from '/@/hooks/web/useMessage';
  46 + import { useDrawer } from '/@/components/Drawer';
  47 + import FeedbackDrawer from './FeedbackDrawer.vue';
  48 + import { feedbackPage, deleteFeedbackManage } from '/@/api/feedback/feedbackManager';
  49 + import { searchFormSchema, columns } from './config.data';
  50 + import { useModal } from '/@/components/Modal';
  51 + import { Modal } from 'ant-design-vue';
  52 + import { JsonPreview } from '/@/components/CodeEditor';
  53 +
  54 + export default defineComponent({
  55 + components: {
  56 + BasicTable,
  57 + TableAction,
  58 + FeedbackDrawer,
  59 + },
  60 + setup() {
  61 + let selectedRowIds = ref<string[]>([]);
  62 + const hasBatchDelete = computed(() => selectedRowIds.value.length <= 0);
  63 + // 复选框事件
  64 + const onSelectRowChange = (selectedRowKeys: string[]) => {
  65 + selectedRowIds.value = selectedRowKeys;
  66 + };
  67 + const searchInfo = reactive<Recordable>({});
  68 + const [registerModal] = useModal();
  69 + // 表格hooks
  70 + const [registerTable, { reload }] = useTable({
  71 + title: '意见反馈列表',
  72 + api: feedbackPage,
  73 + columns,
  74 + showIndexColumn: false,
  75 + clickToRowSelect: false,
  76 + formConfig: {
  77 + labelWidth: 120,
  78 + schemas: searchFormSchema,
  79 + },
  80 + useSearchForm: true,
  81 + showTableSetting: true,
  82 + bordered: true,
  83 + rowSelection: {
  84 + onChange: onSelectRowChange,
  85 + type: 'checkbox',
  86 + },
  87 + rowKey: 'id',
  88 + actionColumn: {
  89 + width: 200,
  90 + title: '操作',
  91 + dataIndex: 'action',
  92 + slots: { customRender: 'action' },
  93 + fixed: 'right',
  94 + },
  95 + });
  96 + // 弹框
  97 + const [registerDrawer, { openDrawer }] = useDrawer();
  98 + const { createMessage } = useMessage();
  99 +
  100 + // 刷新
  101 + const handleSuccess = () => {
  102 + reload();
  103 + };
  104 + // 新增或编辑
  105 + const handleCreateOrEdit = (record: Recordable | null) => {
  106 + if (record) {
  107 + openDrawer(true, {
  108 + isUpdate: true,
  109 + record,
  110 + });
  111 + } else {
  112 + openDrawer(true, {
  113 + isUpdate: false,
  114 + });
  115 + }
  116 + };
  117 + // 删除或批量删除
  118 + const handleDeleteOrBatchDelete = async (record: Recordable | null) => {
  119 + if (record) {
  120 + try {
  121 + await deleteFeedbackManage([record.id]);
  122 + createMessage.success('删除成功');
  123 + handleSuccess();
  124 + } catch (e) {}
  125 + } else {
  126 + try {
  127 + await deleteFeedbackManage(selectedRowIds.value);
  128 + createMessage.success('批量删除成功');
  129 + selectedRowIds.value = [];
  130 + handleSuccess();
  131 + } catch (e) {}
  132 + }
  133 + };
  134 +
  135 + // 树形选择器
  136 + const handleSelect = (organizationId: string) => {
  137 + searchInfo.organizationId = organizationId;
  138 + handleSuccess();
  139 + };
  140 + const handleViewVideo = (record) => {
  141 + openDrawer(true, {
  142 + isUpdate: true,
  143 + record,
  144 + });
  145 + };
  146 + function showData(record: Recordable) {
  147 + try {
  148 + const jsonContact = JSON.parse(record.contact);
  149 + Modal.info({
  150 + title: '当前反馈方式',
  151 + width: 600,
  152 + centered: true,
  153 + maskClosable: true,
  154 + content: h(JsonPreview, { data: jsonContact }),
  155 + });
  156 + } catch (e) {
  157 + console.log('意见反馈', e);
  158 + }
  159 + }
  160 + return {
  161 + searchInfo,
  162 + hasBatchDelete,
  163 + handleCreateOrEdit,
  164 + handleDeleteOrBatchDelete,
  165 + handleSelect,
  166 + handleSuccess,
  167 + registerTable,
  168 + registerDrawer,
  169 + handleViewVideo,
  170 + registerModal,
  171 + showData,
  172 + };
  173 + },
  174 + });
  175 +</script>
... ...