Commit 06cd07c22c7db5228b236ff44e13a2782e9562a9
Merge branch 'sqy_dev' into 'main'
'feat:实时数据(新增前端查询),fix:修复通知管理上传图片,feat:地图增加搜索功能' See merge request huang/yun-teng-iot-front!114
Showing
13 changed files
with
191 additions
and
105 deletions
... | ... | @@ -38,7 +38,6 @@ |
38 | 38 | import 'tinymce/plugins/print'; |
39 | 39 | import 'tinymce/plugins/save'; |
40 | 40 | import 'tinymce/plugins/searchreplace'; |
41 | - import 'tinymce/plugins/spellchecker'; | |
42 | 41 | import 'tinymce/plugins/tabfocus'; |
43 | 42 | // import 'tinymce/plugins/table'; |
44 | 43 | import 'tinymce/plugins/template'; |
... | ... | @@ -289,7 +288,11 @@ |
289 | 288 | return; |
290 | 289 | } |
291 | 290 | const content = editor?.getContent() ?? ''; |
292 | - const val = content?.replace(getUploadingImgName(name), `<img src="${url}"/>`) ?? ''; | |
291 | + const val = | |
292 | + content?.replace( | |
293 | + getUploadingImgName(name), | |
294 | + `<img src="${url}" style="height:6rem;width:6rem"/>` | |
295 | + ) ?? ''; | |
293 | 296 | setValue(editor, val); |
294 | 297 | } |
295 | 298 | ... | ... |
... | ... | @@ -7,6 +7,7 @@ |
7 | 7 | :action="uploadUrl" |
8 | 8 | :showUploadList="false" |
9 | 9 | accept=".jpg,.jpeg,.gif,.png,.webp" |
10 | + :headers="{ 'X-Authorization': 'Bearer' + ' ' + token }" | |
10 | 11 | > |
11 | 12 | <a-button type="primary" v-bind="{ ...getButtonProps }"> |
12 | 13 | {{ t('component.upload.imgUpload') }} |
... | ... | @@ -21,7 +22,8 @@ |
21 | 22 | import { useDesign } from '/@/hooks/web/useDesign'; |
22 | 23 | import { useGlobSetting } from '/@/hooks/setting'; |
23 | 24 | import { useI18n } from '/@/hooks/web/useI18n'; |
24 | - | |
25 | + import { JWT_TOKEN_KEY } from '/@/enums/cacheEnum'; | |
26 | + import { getAuthCache } from '/@/utils/auth'; | |
25 | 27 | export default defineComponent({ |
26 | 28 | name: 'TinymceImageUpload', |
27 | 29 | components: { Upload }, |
... | ... | @@ -37,7 +39,7 @@ |
37 | 39 | emits: ['uploading', 'done', 'error'], |
38 | 40 | setup(props, { emit }) { |
39 | 41 | let uploading = false; |
40 | - | |
42 | + const token: string = getAuthCache(JWT_TOKEN_KEY); | |
41 | 43 | const { uploadUrl } = useGlobSetting(); |
42 | 44 | const { t } = useI18n(); |
43 | 45 | const { prefixCls } = useDesign('tinymce-img-upload'); |
... | ... | @@ -52,7 +54,7 @@ |
52 | 54 | function handleChange(info: Recordable) { |
53 | 55 | const file = info.file; |
54 | 56 | const status = file?.status; |
55 | - const url = file?.response?.url; | |
57 | + const url = file?.response?.fileStaticUri; | |
56 | 58 | const name = file?.name; |
57 | 59 | |
58 | 60 | if (status === 'uploading') { |
... | ... | @@ -75,6 +77,7 @@ |
75 | 77 | uploadUrl, |
76 | 78 | t, |
77 | 79 | getButtonProps, |
80 | + token, | |
78 | 81 | }; |
79 | 82 | }, |
80 | 83 | }); | ... | ... |
... | ... | @@ -4,7 +4,7 @@ |
4 | 4 | // colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration |
5 | 5 | |
6 | 6 | export const plugins = [ |
7 | - 'advlist anchor autolink autosave code codesample directionality fullscreen hr insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus template textpattern visualblocks visualchars wordcount', | |
7 | + 'advlist anchor autolink autosave code codesample directionality fullscreen hr insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace tabfocus template textpattern visualblocks visualchars wordcount', | |
8 | 8 | ]; |
9 | 9 | |
10 | 10 | export const toolbar = [ | ... | ... |
... | ... | @@ -99,7 +99,7 @@ export const alarmSearchSchemas: FormSchema[] = [ |
99 | 99 | }, |
100 | 100 | { |
101 | 101 | field: 'endTime', |
102 | - label: '告警时间范围', | |
102 | + label: '告警时间', | |
103 | 103 | component: 'DatePicker', |
104 | 104 | componentProps: { |
105 | 105 | valueFormat: 'x', |
... | ... | @@ -223,7 +223,6 @@ export const childDeviceSchemas: FormSchema[] = [ |
223 | 223 | colProps: { span: 6 }, |
224 | 224 | component: 'Select', |
225 | 225 | componentProps: { |
226 | - size: 'small', | |
227 | 226 | maxLength: 255, |
228 | 227 | options: [ |
229 | 228 | { label: '待激活', value: 'INACTIVE' }, | ... | ... |
... | ... | @@ -55,6 +55,16 @@ export const columns: BasicColumn[] = [ |
55 | 55 | // 查询字段 |
56 | 56 | export const searchFormSchema: FormSchema[] = [ |
57 | 57 | { |
58 | + field: 'name', | |
59 | + label: '设备名称', | |
60 | + component: 'Input', | |
61 | + colProps: { span: 6 }, | |
62 | + componentProps: { | |
63 | + maxLength: 255, | |
64 | + placeholder: '请输入设备名称', | |
65 | + }, | |
66 | + }, | |
67 | + { | |
58 | 68 | field: 'deviceType', |
59 | 69 | label: '设备类型', |
60 | 70 | component: 'Select', |
... | ... | @@ -80,27 +90,4 @@ export const searchFormSchema: FormSchema[] = [ |
80 | 90 | }, |
81 | 91 | colProps: { span: 6 }, |
82 | 92 | }, |
83 | - { | |
84 | - field: 'name', | |
85 | - label: '设备名称', | |
86 | - component: 'Input', | |
87 | - colProps: { span: 6 }, | |
88 | - componentProps: { | |
89 | - maxLength: 255, | |
90 | - placeholder: '请输入设备名称', | |
91 | - }, | |
92 | - dynamicRules: () => { | |
93 | - return [ | |
94 | - { | |
95 | - required: false, | |
96 | - validator: (_, value) => { | |
97 | - if (String(value).length > 255) { | |
98 | - return Promise.reject('字数不超过255个字'); | |
99 | - } | |
100 | - return Promise.resolve(); | |
101 | - }, | |
102 | - }, | |
103 | - ]; | |
104 | - }, | |
105 | - }, | |
106 | 93 | ]; | ... | ... |
... | ... | @@ -44,16 +44,31 @@ |
44 | 44 | centered |
45 | 45 | > |
46 | 46 | <div> |
47 | - <a-form :label-col="labelCol"> | |
48 | - <a-row :gutter="20" class="pt-4 pl-6"> | |
49 | - <a-col :span="8"> | |
47 | + <a-form :label-col="labelCol" :colon="false"> | |
48 | + <a-row :gutter="20" class="mt-4"> | |
49 | + <a-col :span="20"> | |
50 | + <a-form-item label="搜索位置"> | |
51 | + <AutoComplete | |
52 | + v-model:value="positionState.address" | |
53 | + :options="dataSource" | |
54 | + style="width: 100%" | |
55 | + placeholder="搜索位置" | |
56 | + @search="debounceSearch" | |
57 | + @select="handleSelect" | |
58 | + backfill | |
59 | + /> | |
60 | + </a-form-item> | |
61 | + </a-col> | |
62 | + </a-row> | |
63 | + <a-row :gutter="20" class=""> | |
64 | + <a-col :span="10"> | |
50 | 65 | <a-form-item label="经度"> |
51 | - <Input type="input" v-model:value="positionState.longitude" disabled /> | |
66 | + <Input v-model:value="positionState.longitude" disabled /> | |
52 | 67 | </a-form-item> |
53 | 68 | </a-col> |
54 | - <a-col :span="8"> | |
69 | + <a-col :span="10"> | |
55 | 70 | <a-form-item label="纬度"> |
56 | - <Input type="input" v-model:value="positionState.latitude" disabled /> | |
71 | + <Input v-model:value="positionState.latitude" disabled /> | |
57 | 72 | </a-form-item> |
58 | 73 | </a-col> |
59 | 74 | </a-row> |
... | ... | @@ -68,18 +83,19 @@ |
68 | 83 | import { BasicForm, useForm } from '/@/components/Form'; |
69 | 84 | import { step1Schemas } from '../../config/data'; |
70 | 85 | import { useScript } from '/@/hooks/web/useScript'; |
71 | - import { Input, Divider, Upload, message, Modal, Form, Row, Col } from 'ant-design-vue'; | |
86 | + import { Input, Upload, message, Modal, Form, Row, Col, AutoComplete } from 'ant-design-vue'; | |
72 | 87 | import { EnvironmentTwoTone, PlusOutlined } from '@ant-design/icons-vue'; |
73 | 88 | import { upload } from '/@/api/oss/ossFileUploader'; |
74 | 89 | import { FileItem } from '/@/components/Upload/src/typing'; |
75 | 90 | import { BAI_DU_MAP_URL } from '/@/utils/fnUtils'; |
76 | 91 | import icon from '/@/assets/images/wz.png'; |
92 | + import { useDebounceFn } from '@vueuse/core'; | |
77 | 93 | export default defineComponent({ |
78 | 94 | components: { |
79 | 95 | BasicForm, |
80 | 96 | Input, |
97 | + AutoComplete, | |
81 | 98 | [Input.Group.name]: Input.Group, |
82 | - [Divider.name]: Divider, | |
83 | 99 | Upload, |
84 | 100 | EnvironmentTwoTone, |
85 | 101 | PlusOutlined, |
... | ... | @@ -101,16 +117,7 @@ |
101 | 117 | emits: ['next'], |
102 | 118 | setup(props, { emit }) { |
103 | 119 | const devicePic = ref(''); |
104 | - let positionState = reactive<{ | |
105 | - longitude: string; | |
106 | - latitude: string; | |
107 | - description?: string; | |
108 | - address: string; | |
109 | - }>({ | |
110 | - longitude: '', | |
111 | - latitude: '', | |
112 | - address: '', | |
113 | - }); | |
120 | + | |
114 | 121 | const [register, { validate, resetFields, setFieldsValue, getFieldsValue, updateSchema }] = |
115 | 122 | useForm({ |
116 | 123 | labelWidth: 100, |
... | ... | @@ -169,6 +176,35 @@ |
169 | 176 | } |
170 | 177 | }; |
171 | 178 | |
179 | + let positionState = reactive<{ | |
180 | + longitude: string; | |
181 | + latitude: string; | |
182 | + description?: string; | |
183 | + address: string; | |
184 | + map: null; | |
185 | + marker: null; | |
186 | + }>({ | |
187 | + longitude: '', | |
188 | + latitude: '', | |
189 | + address: '', | |
190 | + map: null, | |
191 | + marker: null, | |
192 | + }); | |
193 | + /** | |
194 | + * 逆地址解析函数(根据坐标点获取详细地址) | |
195 | + * @param {Object} point 百度地图坐标点,必传 | |
196 | + */ | |
197 | + function getAddrByPoint(point) { | |
198 | + let geco = new BMap.Geocoder(); | |
199 | + geco.getLocation(point, function (res) { | |
200 | + positionState.marker.setPosition(point); //重新设置标注的地理坐标 | |
201 | + positionState.map.panTo(point); //将地图的中心点更改为给定的点 | |
202 | + positionState.address = res.address; //记录该点的详细地址信息 | |
203 | + positionState.longitude = point.lng; //记录当前坐标点 | |
204 | + positionState.latitude = point.lat; | |
205 | + }); | |
206 | + } | |
207 | + | |
172 | 208 | // 地图 |
173 | 209 | const wrapRef = ref<HTMLDivElement | null>(null); |
174 | 210 | const { toPromise } = useScript({ src: BAI_DU_MAP_URL }); |
... | ... | @@ -180,48 +216,94 @@ |
180 | 216 | const BMap = (window as any).BMap; |
181 | 217 | if (!wrapEl) return; |
182 | 218 | let preMarker = null; |
183 | - const map = new BMap.Map(wrapEl); | |
219 | + positionState; | |
220 | + positionState.map = new BMap.Map(wrapEl, { enableMapClick: false }); | |
184 | 221 | let myIcon = new BMap.Icon(icon, new BMap.Size(20, 30)); |
185 | 222 | const point = new BMap.Point(Number(longitude), Number(latitude)); |
186 | - let marker = new BMap.Marker(point, { icon: myIcon }); | |
187 | - if (marker) { | |
188 | - map.removeOverlay(preMarker); | |
223 | + positionState.marker = new BMap.Marker(point, { icon: myIcon, enableDragging: true }); | |
224 | + if (positionState.marker) { | |
225 | + positionState.map.removeOverlay(preMarker); | |
189 | 226 | } |
190 | - map.addOverlay(marker); | |
191 | - preMarker = marker; | |
192 | - map.centerAndZoom(point, 15); | |
193 | - map.enableScrollWheelZoom(true); | |
194 | - map.addEventListener('click', (e) => { | |
195 | - const { lat, lng } = e.point; | |
196 | - positionState.latitude = lat + ''; | |
197 | - positionState.longitude = lng + ''; | |
198 | - let gc = new BMap.Geocoder(); | |
199 | - let newPoint = new BMap.Point(lng, lat); | |
227 | + positionState.map.addOverlay(positionState.marker); | |
228 | + preMarker = positionState.marker; | |
229 | + positionState.map.centerAndZoom(point, 15); | |
230 | + positionState.map.enableScrollWheelZoom(true); | |
231 | + let navigationControl = new BMap.NavigationControl({ | |
232 | + //创建一个特定样式的地图平移缩放控件 | |
233 | + anchor: BMAP_ANCHOR_TOP_RIGHT, //靠右上角位置 | |
234 | + type: BMAP_NAVIGATION_CONTROL_LARGE, //SMALL控件类型 | |
235 | + }); | |
236 | + positionState.map.addControl(navigationControl); //将控件添加到地图 | |
200 | 237 | |
201 | - // 添加锚点 | |
202 | - if (!e.overlay) { | |
203 | - let marker = new BMap.Marker(e.point, { icon: myIcon }); | |
204 | - map.removeOverlay(preMarker); | |
205 | - map.addOverlay(marker); | |
206 | - preMarker = marker; | |
207 | - } | |
208 | - //获取详细的地址,精确到街道的名称 | |
209 | - gc.getLocation(newPoint, (rs) => { | |
210 | - let addComp = rs.addressComponents; | |
211 | - let address = addComp.city + addComp.district + addComp.street + addComp.streetNumber; | |
212 | - positionState.address = address; | |
213 | - }); | |
238 | + positionState.marker.addEventListener('dragend', function (e) { | |
239 | + getAddrByPoint(e.point); //拖拽结束后调用逆地址解析函数,e.point为拖拽后的地理坐标 | |
240 | + }); | |
241 | + positionState.map.addEventListener('click', (e) => { | |
242 | + getAddrByPoint(e.point); | |
214 | 243 | }); |
215 | 244 | } |
245 | + const dataSource = ref<any[]>([]); | |
246 | + | |
247 | + const onSearch = (searchText: string) => { | |
248 | + if (!searchText) { | |
249 | + dataSource.value = []; | |
250 | + return; | |
251 | + } | |
252 | + let local = new BMap.LocalSearch(positionState.map, { | |
253 | + onSearchComplete(res) { | |
254 | + //检索完成后的回调函数 | |
255 | + if (local.getStatus() == BMAP_STATUS_SUCCESS) { | |
256 | + const searchArr = []; | |
257 | + for (let i = 0; i < res.getCurrentNumPois(); i++) { | |
258 | + const item = res.getPoi(i); | |
259 | + console.log(item); | |
260 | + searchArr.push({ | |
261 | + label: item.address + item.title, | |
262 | + value: item.address + item.title, | |
263 | + point: item.point, | |
264 | + }); | |
265 | + } | |
266 | + dataSource.value = searchArr; | |
267 | + } | |
268 | + }, | |
269 | + }); //调用search方法,根据检索词searchText发起检索 | |
270 | + local.search(searchText); | |
271 | + console.log(dataSource); | |
272 | + }; | |
273 | + // 防抖函数包装一下,防止频繁执行 | |
274 | + const debounceSearch = useDebounceFn(onSearch, 500); | |
275 | + function handleSelect(value, option) { | |
276 | + dataSource.value = []; | |
277 | + console.log(option); | |
278 | + positionState.address = option.value; //记录详细地址,含建筑物名 | |
279 | + const { lat, lng } = option.point; | |
280 | + positionState.latitude = lat; //记录当前选中地址坐标 | |
281 | + positionState.longitude = lng; //记录当前选中地址坐标 | |
282 | + positionState.map.clearOverlays(); //清除地图上所有覆盖物 | |
283 | + let myIcon = new BMap.Icon(icon, new BMap.Size(20, 30)); | |
284 | + positionState.marker = new BMap.Marker(option.point, { | |
285 | + icon: myIcon, | |
286 | + enableDragging: true, | |
287 | + }); //根据所选坐标重新创建Marker | |
288 | + positionState.marker.addEventListener('dragend', function (e) { | |
289 | + getAddrByPoint(e.point); //拖拽结束后调用逆地址解析函数,e.point为拖拽后的地理坐标 | |
290 | + }); | |
291 | + positionState.map.addOverlay(positionState.marker); //将覆盖物重新添加到地图中 | |
292 | + positionState.map.panTo(option.point); //将地图的中心点更改为选定坐标点 | |
293 | + } | |
294 | + | |
216 | 295 | // 确定选择的位置 |
217 | 296 | const handleOk = () => { |
218 | 297 | visible.value = false; |
219 | 298 | }; |
220 | 299 | // 取消选择位置 |
221 | 300 | const handleCancel = () => { |
301 | + dataSource.value = []; | |
302 | + | |
222 | 303 | for (let key in positionState) { |
223 | 304 | positionState[key] = ''; |
224 | 305 | } |
306 | + console.log(positionState); | |
225 | 307 | }; |
226 | 308 | // 父组件调用更新字段值的方法 |
227 | 309 | function parentSetFieldsValue(data) { |
... | ... | @@ -280,7 +362,7 @@ |
280 | 362 | handleOk, |
281 | 363 | handleCancel, |
282 | 364 | wrapRef, |
283 | - labelCol: { style: { width: '40px' } }, | |
365 | + labelCol: { style: { width: '100px' } }, | |
284 | 366 | parentSetFieldsValue, |
285 | 367 | parentGetFieldsValue, |
286 | 368 | parentValidate, |
... | ... | @@ -288,6 +370,10 @@ |
288 | 370 | parentResetPositionState, |
289 | 371 | disabledDeviceType, |
290 | 372 | nextStep, |
373 | + onSearch, | |
374 | + handleSelect, | |
375 | + dataSource, | |
376 | + debounceSearch, | |
291 | 377 | }; |
292 | 378 | }, |
293 | 379 | }); | ... | ... |
... | ... | @@ -25,7 +25,7 @@ |
25 | 25 | </div> |
26 | 26 | </template> |
27 | 27 | <script lang="ts"> |
28 | - import { defineComponent, onMounted } from 'vue'; | |
28 | + import { defineComponent } from 'vue'; | |
29 | 29 | import { Tag } from 'ant-design-vue'; |
30 | 30 | import { DeviceState } from '/@/api/device/model/deviceModel'; |
31 | 31 | import { BasicTable, useTable } from '/@/components/Table'; |
... | ... | @@ -44,19 +44,14 @@ |
44 | 44 | }, |
45 | 45 | }, |
46 | 46 | setup(props) { |
47 | - console.log(123); | |
48 | - onMounted(() => { | |
49 | - console.log(props.fromId); | |
50 | - }); | |
51 | 47 | const [registerTable] = useTable({ |
52 | 48 | api: getChildDevicePage, |
53 | 49 | columns: childDeviceColumns, |
54 | 50 | formConfig: { |
55 | - labelWidth: 120, | |
51 | + labelWidth: 100, | |
56 | 52 | schemas: childDeviceSchemas, |
57 | 53 | }, |
58 | 54 | beforeFetch: (data) => { |
59 | - console.log(props.fromId); | |
60 | 55 | Reflect.set(data, 'fromId', props.fromId); |
61 | 56 | }, |
62 | 57 | useSearchForm: true, | ... | ... |
... | ... | @@ -48,7 +48,7 @@ |
48 | 48 | recordList: Array<socketDataType>(), |
49 | 49 | }); |
50 | 50 | const { createMessage } = useMessage(); |
51 | - const [registerTable] = useTable({ | |
51 | + const [registerTable, { getForm, setTableData }] = useTable({ | |
52 | 52 | columns: realTimeDataColumns, |
53 | 53 | showTableSetting: true, |
54 | 54 | bordered: true, |
... | ... | @@ -56,18 +56,42 @@ |
56 | 56 | dataSource: state.recordList, |
57 | 57 | useSearchForm: true, |
58 | 58 | formConfig: { |
59 | - labelWidth: 120, | |
59 | + labelWidth: 100, | |
60 | 60 | schemas: [ |
61 | 61 | { |
62 | - field: 'icon', | |
63 | - label: '设备配置', | |
64 | - component: 'Select', | |
62 | + field: 'key', | |
63 | + label: '键/值', | |
64 | + component: 'Input', | |
65 | 65 | colProps: { span: 6 }, |
66 | 66 | componentProps: { |
67 | + placeholder: '请输入 键/值', | |
67 | 68 | maxLength: 255, |
68 | 69 | }, |
69 | 70 | }, |
70 | 71 | ], |
72 | + // 自定义前端查询 | |
73 | + submitFunc() { | |
74 | + const { getFieldsValue } = getForm(); | |
75 | + const { key } = getFieldsValue(); | |
76 | + if (!key) { | |
77 | + setTableData(state.recordList); | |
78 | + return; | |
79 | + } | |
80 | + const newRecordList = state.recordList.find((item) => { | |
81 | + if (item.key === key) return item; | |
82 | + console.log('--------------------------', item); | |
83 | + if (item.value === key) return item; | |
84 | + }); | |
85 | + console.log(newRecordList); | |
86 | + if (!newRecordList) { | |
87 | + setTableData([]); | |
88 | + } else { | |
89 | + setTableData([newRecordList]); | |
90 | + } | |
91 | + }, | |
92 | + resetFunc() { | |
93 | + setTableData(state.recordList); | |
94 | + }, | |
71 | 95 | }, |
72 | 96 | }); |
73 | 97 | |
... | ... | @@ -115,7 +139,6 @@ |
115 | 139 | createMessage.error('webSocket连接超时,请联系管理员'); |
116 | 140 | }, |
117 | 141 | }); |
118 | - | |
119 | 142 | return { |
120 | 143 | registerTable, |
121 | 144 | }; | ... | ... |
... | ... | @@ -101,9 +101,8 @@ export const formSchema: FormSchema[] = [ |
101 | 101 | field: 'content', |
102 | 102 | component: 'Input', |
103 | 103 | colProps: { span: 24 }, |
104 | - label: '内容', | |
104 | + label: '通知内容', | |
105 | 105 | required: true, |
106 | - defaultValue: '', | |
107 | 106 | render: ({ model, field }) => { |
108 | 107 | return h(Tinymce, { |
109 | 108 | value: model[field], |
... | ... | @@ -143,15 +142,6 @@ export const formSchema: FormSchema[] = [ |
143 | 142 | }, |
144 | 143 | ifShow: ({ values }) => isOrg(Reflect.get(values, 'receiverType')), |
145 | 144 | }, |
146 | - { | |
147 | - field: 'abc', | |
148 | - component: 'Input', | |
149 | - label: '', | |
150 | - colProps: { | |
151 | - span: 12, | |
152 | - }, | |
153 | - slot: 'add', | |
154 | - }, | |
155 | 145 | ]; |
156 | 146 | |
157 | 147 | export const searchFormSchema: FormSchema[] = [ | ... | ... |