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> |