Commit 81906bdb60ce220da9894a7faea8b550bd5e12ac
Merge branch 'f-dev' into 'main'
feat:新增意见反馈功能 See merge request huang/yun-teng-iot-front!203
Showing
5 changed files
with
446 additions
and
0 deletions
src/api/feedback/feedbackManager.ts
0 → 100644
| 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 | +}; |
src/api/feedback/model/feedbackModel.ts
0 → 100644
| 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 | +} |
src/views/system/feedback/FeedbackDrawer.vue
0 → 100644
| 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> |
src/views/system/feedback/config.data.ts
0 → 100644
| 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 | +]; |
src/views/system/feedback/index.vue
0 → 100644
| 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> |