Commit a7b56d1d723d91d7c008e14a7a58e56d604c6ef4

Authored by sqy
1 parent 51061442

'feat:实时数据(新增前端查询),fix:修复通知管理上传图片,feat:地图增加搜索功能'

... ... @@ -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 });
... ...
... ... @@ -42,7 +42,7 @@
42 42 columns: alarmColumns,
43 43 useSearchForm: true,
44 44 formConfig: {
45   - labelWidth: 120,
  45 + labelWidth: 100,
46 46 schemas: alarmSearchSchemas,
47 47 },
48 48 showTableSetting: true,
... ...
... ... @@ -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 };
... ...
... ... @@ -162,7 +162,7 @@
162 162 api: devicePage,
163 163 columns,
164 164 formConfig: {
165   - labelWidth: 120,
  165 + labelWidth: 100,
166 166 schemas: searchFormSchema,
167 167 resetFunc: resetFn,
168 168 },
... ...
... ... @@ -26,7 +26,7 @@
26 26 render: ({ model, field }) => {
27 27 return h(Tinymce, {
28 28 value: model[field],
29   - showImageUpload: false,
  29 + // showImageUpload: false,
30 30 onChange: (value: string) => {
31 31 model[field] = value;
32 32 },
... ...
... ... @@ -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[] = [
... ...
... ... @@ -38,7 +38,7 @@
38 38 },
39 39 {
40 40 field: 'type',
41   - label: '类型:',
  41 + label: '类型',
42 42 render: (_, { type }) => {
43 43 return type === 'NOTICE'
44 44 ? '公告'
... ...