new-map
ihzero 2022-11-17 16:39:45 +08:00
parent 50df3f3e1c
commit e55f36133d
18 changed files with 508 additions and 83 deletions

View File

@ -6,7 +6,7 @@ VITE_PUBLIC_PATH = /
# Cross-domain proxy, you can configure multiple
# Please note that no line breaks
VITE_PROXY = [["/api","http://lcny-api.peidikeji.cn"]]
VITE_PROXY = [["/api","http://lcny-api.peidikeji.cn"],["/upload","http://lcny-api.peidikeji.cn/api/web/upload"]]
# Delete console
VITE_DROP_CONSOLE = false
@ -15,7 +15,7 @@ VITE_DROP_CONSOLE = false
VITE_GLOB_API_URL=/api
# File upload address optional
VITE_GLOB_UPLOAD_URL=/api
VITE_GLOB_UPLOAD_URL=/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=

View File

@ -855,3 +855,59 @@ export function updateFriendinks(params, mode: ErrorMessageMode = 'modal') {
},
)
}
/**
* @description:
*/
export function createFriendinks(data, mode: ErrorMessageMode = 'modal') {
return defHttp.post(
{
url: `/api/friend-links`,
data,
},
{
errorMessageMode: mode,
},
)
}
/**
* @description:
*/
export function deleteFriendinks(id, mode: ErrorMessageMode = 'modal') {
return defHttp.delete(
{
url: `/api/friend-links/${id}`,
},
{
errorMessageMode: mode,
},
)
}
/**
* @description:
*/
export function getWarningLogs(params, mode: ErrorMessageMode = 'modal') {
return defHttp.get(
{
url: `/api/device-warning-logs`,
params,
},
{
errorMessageMode: mode,
isTransformResponse: false,
},
)
}
/**
* @description:
*/
export function markWarningLogs(id, mode: ErrorMessageMode = 'modal') {
return defHttp.put(
{
url: `/api/device-warning-mark/${id}`,
},
{
errorMessageMode: mode,
},
)
}

View File

@ -18,9 +18,16 @@ export function uploadApi(
) {
return defHttp.uploadFile<UploadApiResult>(
{
url: uploadUrl + '/api/web/upload',
url: uploadUrl,
onUploadProgress,
},
params,
)
}
export function uploadPostApi(data) {
return defHttp.post({
url: '/api/web/upload',
data,
})
}

View File

@ -1,11 +1,13 @@
<template>
<div :class="[prefixCls, { fullscreen }]">
<!-- :action="uploadUrl" -->
<Upload
name="file"
:customRequest="customRequest"
multiple
@change="handleChange"
:action="uploadUrl"
:showUploadList="false"
:headers="headers"
accept=".jpg,.jpeg,.gif,.png,.webp"
>
<a-button type="primary" v-bind="{ ...getButtonProps }">
@ -15,12 +17,14 @@
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { defineComponent, computed } from 'vue'
import { Upload } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useGlobSetting } from '/@/hooks/setting';
import { useI18n } from '/@/hooks/web/useI18n';
import { Upload } from 'ant-design-vue'
import { useDesign } from '/@/hooks/web/useDesign'
import { useGlobSetting } from '/@/hooks/setting'
import { useI18n } from '/@/hooks/web/useI18n'
import { getToken } from '/@/utils/auth'
import { uploadPostApi } from '/@/api/upload'
export default defineComponent({
name: 'TinymceImageUpload',
@ -36,48 +40,67 @@
},
emits: ['uploading', 'done', 'error'],
setup(props, { emit }) {
let uploading = false;
const { uploadUrl } = useGlobSetting();
const { t } = useI18n();
const { prefixCls } = useDesign('tinymce-img-upload');
let uploading = false
const headers = {
Authorization: `Bearer ` + getToken(),
}
const { uploadUrl } = useGlobSetting()
const { t } = useI18n()
const { prefixCls } = useDesign('tinymce-img-upload')
const getButtonProps = computed(() => {
const { disabled } = props;
const { disabled } = props
return {
disabled,
};
});
}
})
function customRequest(file) {
const form = new FormData()
form.append('file', file.file)
form.append('contractName', file.file.name)
form.append('description', file.file.name)
//
uploadPostApi(form)
.then((res) => {
file.onSuccess(res)
})
.catch((err) => {
file.onError(err)
})
}
function handleChange(info: Recordable) {
const file = info.file;
const status = file?.status;
const url = file?.response?.url;
const name = file?.name;
const file = info.file
const status = file?.status
const url = file?.response?.file
const name = file?.name
if (status === 'uploading') {
if (!uploading) {
emit('uploading', name);
uploading = true;
emit('uploading', name)
uploading = true
}
} else if (status === 'done') {
emit('done', name, url);
uploading = false;
emit('done', name, url)
uploading = false
} else if (status === 'error') {
emit('error');
uploading = false;
emit('error')
uploading = false
}
}
return {
customRequest,
headers,
prefixCls,
handleChange,
uploadUrl,
t,
getButtonProps,
};
}
},
});
})
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-tinymce-img-upload';

View File

@ -250,8 +250,6 @@
for (const item of fileListRef.value) {
const { status, responseData } = item
if (status === UploadResultStatus.SUCCESS && responseData) {
console.log(responseData?.data?.file)
fileList.push(responseData?.data?.file)
}
}

View File

@ -27,6 +27,8 @@ export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'
export const YEAR_KEY = 'YEAR_KEY__'
export const INIT_TIME = 'INIT_TIME_'
export enum CacheTypeEnum {
SESSION,
LOCAL,

View File

@ -19,6 +19,14 @@ const main: AppRouteModule = {
title: '设备管理',
},
},
{
path: 'warning',
name: 'DeviceWarning',
component: () => import('/@/views/device/warning/index.vue'),
meta: {
title: '警报明细',
},
},
],
}

View File

@ -12,6 +12,7 @@ import {
APP_LOCAL_CACHE_KEY,
APP_SESSION_CACHE_KEY,
MULTIPLE_TABS_KEY,
INIT_TIME,
} from '/@/enums/cacheEnum'
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting'
import { toRaw } from 'vue'
@ -23,6 +24,7 @@ interface BasicStore {
[ROLES_KEY]: string[]
[PROJ_CFG_KEY]: ProjectConfig
[MULTIPLE_TABS_KEY]: RouteLocationNormalized[]
[INIT_TIME]: string | number | null | undefined
}
type LocalStore = BasicStore

View File

@ -0,0 +1,78 @@
<template>
<div>
<BasicTable @register="registerTable">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<div class="flex items-center justify-center">
<TableAction
:actions="[
{
label: '标记',
popConfirm: {
title: '是否确认标记',
placement: 'topRight',
confirm: handleMark.bind(null, record),
},
ifShow: record.status == 0,
},
]"
/>
</div>
</template>
</template>
</BasicTable>
</div>
</template>
<script lang="ts">
import { BasicTable, useTable, TableAction } from '/@/components/Table'
import { getWarningLogs, markWarningLogs } from '/@/api/sys/user'
import { columns, searchFormSchema } from './warning.data'
import { message } from 'ant-design-vue'
export default {
components: {
BasicTable,
TableAction,
},
setup() {
const [registerTable, { reload }] = useTable({
title: '账号列表',
api: async (e) => {
const { data, meta } = await getWarningLogs({ ...e })
return {
items: data,
total: meta?.total,
}
},
rowKey: 'id',
columns,
formConfig: {
labelWidth: 80,
schemas: searchFormSchema,
},
useSearchForm: true,
showTableSetting: true,
bordered: true,
showIndexColumn: true,
})
const handleSuccess = () => {
message.success('操作成功')
reload()
}
const handleMark = async (record: Recordable) => {
await markWarningLogs(record.id)
message.success('删除成功')
reload()
}
return {
handleMark,
registerTable,
handleSuccess,
}
},
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,90 @@
import { BasicColumn, FormSchema } from '/@/components/Table'
import dayjs from 'dayjs'
import { h } from 'vue'
import { Tag } from 'ant-design-vue'
import { ColEx } from '/@/components/Form/src/types'
const colProps: Partial<ColEx> = {
xs: 24,
sm: 12,
md: 8,
lg: 6,
xl: 6,
xxl: 4,
}
const statusOptions = [
{
value: 0,
color: 'red',
label: '未处理',
},
{
value: 1,
color: 'green',
label: '已处理',
},
{
value: 2,
color: 'pink',
label: '已忽略',
},
]
export const columns: BasicColumn[] = [
{
title: 'ID',
dataIndex: 'id',
},
{
title: '内容',
dataIndex: 'content',
},
{
title: '等级',
dataIndex: 'lv',
width: 100,
},
{
title: '状态',
dataIndex: 'status',
width: 100,
customRender: ({ record }) => {
const status = record.status
const list = statusOptions
const item = list.find((e) => e.value === status)
const color = item?.color ?? 'red'
const text = item?.label ?? status
return h(Tag, { color: color }, () => text)
},
},
{
title: '开始时间',
dataIndex: 'created_at',
width: 180,
customRender: ({ text }) => {
if (!text) return ''
return dayjs.unix(text).format('YYYY-MM-DD HH:mm:ss')
},
},
{
width: 90,
title: '操作',
dataIndex: 'action',
align: 'center',
fixed: undefined,
},
]
export const searchFormSchema: FormSchema[] = [
{
field: 'status',
label: '状态',
component: 'Select',
componentProps: {
options: statusOptions,
},
colProps,
},
]

View File

@ -16,26 +16,42 @@
import { accountFormSchema } from './links.data'
import { BasicModal, useModalInner } from '/@/components/Modal'
import { defaultsDeep } from 'lodash-es'
import { createDevice, getDeviceTypes, updateDevice } from '/@/api/sys/other'
import { createFriendinks, updateFriendinks } from '/@/api/sys/user'
const emits = defineEmits(['success', 'register'])
const isUpdate = ref(false)
const getTitle = computed(() => (!isUpdate.value ? '新增链接' : '编辑链接'))
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
labelWidth: 60,
const [registerForm, { resetFields, setFieldsValue, updateSchema, validate }] = useForm({
labelWidth: 80,
baseColProps: { span: 24 },
schemas: accountFormSchema,
showActionButtonGroup: false,
})
const isTrueFalse = (e) => !!e
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
resetFields()
setModalProps({ confirmLoading: false })
isUpdate.value = data?.isUpdate
if (unref(isUpdate)) {
const obj = Object.assign({}, { ...data, ...data?.extends })
const deviceTypes = await getDeviceTypes()
updateSchema({
field: 'type',
dynamicDisabled: true,
})
console.log({
...obj,
agricultural_base_id: obj.base_id,
is_show: isTrueFalse(obj.is_show),
is_recommend: isTrueFalse(obj.is_recommend),
[`content${obj.type}`]: obj.type == 2 ? [obj.content] : obj.content,
})
await setFieldsValue({
...obj,
agricultural_base_id: obj.base_id,
type: formatDataByObject(deviceTypes).find((e) => e.label == obj.type)?.value,
is_show: isTrueFalse(obj.is_show),
is_recommend: isTrueFalse(obj.is_recommend),
[`content${obj.type}`]: obj.type == 2 ? [obj.content] : obj.content,
})
}
})
@ -56,27 +72,30 @@
return object
}
const handleSubmit = async () => {
const values = await validate()
console.log(values)
try {
const values = await validate()
let params: any = {}
for (const key in values) {
params = defaultsDeep({}, params, setValue(key.split('.'), values[key]))
}
if (params.type == 2) {
params.content = params.content2[0]
} else {
params.content = params[`content${params.type}`]
}
// try {
// const values = await validate()
// let params = {}
// for (const key in values) {
// params = defaultsDeep({}, params, setValue(key.split('.'), values[key]))
// }
// setModalProps({ confirmLoading: true })
// if (values.id) {
// //
// await updateDevice(values.id, params)
// } else {
// //
// await createDevice(params)
// }
// closeModal()
// emits('success')
// } finally {
// setModalProps({ confirmLoading: false })
// }
setModalProps({ confirmLoading: true })
if (values.id) {
//
await updateFriendinks(params)
} else {
//
await createFriendinks(params)
}
closeModal()
emits('success')
} finally {
setModalProps({ confirmLoading: false })
}
}
</script>

View File

@ -4,6 +4,25 @@
<template #toolbar>
<a-button type="primary" @click="handleCreate"> </a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<div class="flex items-center justify-center">
<TableAction
:actions="[
{ label: '编辑', onClick: handleEdit.bind(null, record) },
{
label: '删除',
popConfirm: {
title: '是否确认删除',
placement: 'topRight',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</div>
</template>
</template>
</BasicTable>
<LinksDrawer @register="registerModal" @success="handleSuccess" />
@ -11,9 +30,9 @@
</template>
<script lang="ts">
import { BasicTable, useTable } from '/@/components/Table'
import { BasicTable, useTable, TableAction } from '/@/components/Table'
import { reactive, toRefs } from 'vue'
import { getFriendinks } from '/@/api/sys/user'
import { getFriendinks, deleteFriendinks } from '/@/api/sys/user'
import { columns, searchFormSchema } from './links.data'
import { useModal } from '/@/components/Modal'
import LinksDrawer from './LinksDrawer.vue'
@ -22,6 +41,7 @@
components: {
BasicTable,
LinksDrawer,
TableAction,
},
setup() {
const state = reactive({})
@ -58,7 +78,21 @@
})
}
const handleEdit = (record: Recordable) => {
openModal(true, {
...record,
isUpdate: true,
})
}
const handleDelete = async (record: Recordable) => {
await deleteFriendinks(record.id)
message.success('删除成功')
reload()
}
return {
handleEdit,
handleDelete,
registerTable,
registerModal,
handleSuccess,

View File

@ -1,5 +1,4 @@
import { BasicColumn } from '/@/components/Table'
import { FormSchema } from '/@/components/Table'
import { BasicColumn, FormSchema } from '/@/components/Table'
import dayjs from 'dayjs'
import { h } from 'vue'
import { Tag } from 'ant-design-vue'
@ -40,12 +39,16 @@ export const columns: BasicColumn[] = [
{
title: '名称',
dataIndex: 'name',
},
{
title: '排序',
dataIndex: 'sort',
width: 100,
},
{
title: '类型',
dataIndex: 'type',
width: 60,
width: 100,
customRender: ({ record }) => {
const status = record.type
@ -59,7 +62,7 @@ export const columns: BasicColumn[] = [
{
title: '推荐',
dataIndex: 'is_recommend',
width: 180,
width: 100,
customRender: ({ record }) => {
if (!Reflect.has(record, 'pendingRecommendStatus')) {
record.pendingRecommendStatus = false
@ -94,7 +97,7 @@ export const columns: BasicColumn[] = [
{
title: '状态',
dataIndex: 'is_show',
width: 180,
width: 100,
customRender: ({ record }) => {
if (!Reflect.has(record, 'pendingShowStatus')) {
record.pendingShowStatus = false
@ -135,6 +138,13 @@ export const columns: BasicColumn[] = [
return dayjs.unix(text).format('YYYY-MM-DD HH:mm:ss')
},
},
{
width: 180,
title: '操作',
dataIndex: 'action',
align: 'center',
fixed: undefined,
},
]
export const searchFormSchema: FormSchema[] = [
@ -171,6 +181,42 @@ export const accountFormSchema: FormSchema[] = [
return !!values.id
},
},
{
field: 'name',
label: '名称',
required: true,
component: 'Input',
},
{
field: 'sort',
label: '排序',
required: true,
defaultValue: 1,
component: 'InputNumber',
},
{
field: 'is_recommend',
label: '推荐',
required: true,
defaultValue: false,
component: 'Switch',
componentProps: {
checkedChildren: '是',
unCheckedChildren: '否',
},
},
{
field: 'is_show',
label: '状态',
required: true,
defaultValue: true,
componentProps: {
checkedChildren: '开启',
unCheckedChildren: '关闭',
},
component: 'Switch',
},
{
field: 'type',
label: '类型',
@ -182,7 +228,7 @@ export const accountFormSchema: FormSchema[] = [
},
},
{
field: 'content',
field: 'content3',
label: '内容',
required: true,
component: 'Input',
@ -199,14 +245,17 @@ export const accountFormSchema: FormSchema[] = [
},
},
{
field: 'content',
field: 'content2',
label: '地址',
required: true,
component: 'Upload',
componentProps: {
maxSize: 10,
maxSize: 30,
maxNumber: 1,
showPreviewNumber: false,
multiple: false,
emptyHidePreview: true,
accept: ['.mp4'],
api: uploadApi,
},
ifShow: ({ values }) => {
@ -214,7 +263,7 @@ export const accountFormSchema: FormSchema[] = [
},
},
{
field: 'content',
field: 'content1',
label: '地址',
required: true,
component: 'Input',

View File

@ -1,7 +1,11 @@
import { BasicColumn } from '/@/components/Table'
import { FormSchema } from '/@/components/Table'
import dayjs from 'dayjs'
const options = [
{ label: '创建', value: 'create' },
{ label: '修改', value: 'update' },
{ label: '删除', value: 'delete' },
]
export const columns: BasicColumn[] = [
{
title: '操作人',
@ -12,6 +16,9 @@ export const columns: BasicColumn[] = [
title: '动作',
dataIndex: 'type',
width: 180,
customRender: ({ text }) => {
return options.find((e) => e.value == text)?.label
},
},
{
title: '详情',
@ -35,11 +42,7 @@ export const searchFormSchema: FormSchema[] = [
label: '动作',
component: 'Select',
componentProps: {
options: [
{ label: 'create', value: 'create' },
{ label: 'update', value: 'update' },
{ label: 'delete', value: 'delete' },
],
options,
},
colProps: { span: 8 },
},

View File

@ -62,6 +62,7 @@
type: 'line',
smooth: false,
// symbol: 'none',
stack: 'Total',
itemStyle: {
color: color.itemColor,
},

View File

@ -26,6 +26,7 @@
import { useVisualizationStore } from '/@/store/modules/visualization'
import { chartBarColors } from './colors'
import { useVContext } from '../useVContext'
import { add } from 'lodash-es'
export default defineComponent({
components: {
Box,
@ -83,7 +84,6 @@
legendData: [] as any,
series: [] as any,
}
Data.series.forEach(({ name, data, diffs }, index) => {
const color = chartBarColors[index % chartBarColors.length]
obj.legendData.push(name)
@ -146,7 +146,7 @@
if (item) {
const min = item.data[e.dataIndex] ?? 0
const diff = item.diffs[e.dataIndex] ?? 0
const sum = min + diff
const sum = add(Number(min), Number(diff))
str += `${e.marker}<span style="width:50px;display: inline-block;">${e.seriesName}</span> ${min}-${sum}<br>`
}
}

View File

@ -66,7 +66,8 @@
import { getFriendLinks } from '/@/api/sys/other'
import { onBeforeMount, ref } from 'vue'
import LinkModal from '../LinkModal.vue'
import { useVContext } from '../useVContext'
const { rootEmitter } = useVContext()
const getPopupContainer = (trigger) => {
return trigger.parentElement
}
@ -106,7 +107,7 @@
const find = _type.find((e) => e.type == item.type)
return {
...acc,
[find?.value ?? '其他']: [...(acc[item.type] ?? []), item],
[find?.value ?? '其他']: [...(acc[find.value] ?? []), item],
}
}, {})
}
@ -118,12 +119,15 @@
} else {
visibleModal.value = true
}
console.log(e)
}
onBeforeMount(() => {
getDataF()
getData()
rootEmitter.on('interval:auto', () => {
getDataF()
getData()
})
})
</script>

View File

@ -2,7 +2,7 @@
<ScaleScreen :boxStyle="{ background: '#020603' }" :width="3120" :height="760" :autoScale="true">
<!-- <div class="overflow-y-scroll"> -->
<!-- w-3120px h-760px -->
<div class="flex flex-col h-full bg-img relative">
<div class="flex flex-col h-full bg-img relative visualization—xx">
<canvas
class="absolute left-0 top-0 w-full h-full overflow-hidden"
ref="cavsRef"
@ -87,8 +87,12 @@
import { createVContext } from './useVContext'
import mitt from '/@/utils/mitt'
import Build from './components/cavas'
import dayjs from 'dayjs'
import { getWarningLogs } from '/@/api/sys/user'
import { notification } from 'ant-design-vue'
// import Am from './components/Star'
const initTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
localStorage.removeItem('warning_id')
export default defineComponent({
components: {
[Modal.name]: Modal,
@ -120,6 +124,40 @@
baseData: reactive({}),
})
async function getWarning() {
const ids = localStorage.getItem('warning_id')?.split(',') ?? []
const { data } = await getWarningLogs({ per_page: 10, page: 1, start_time: initTime })
const fliterData = data.filter((e) => ids.findIndex((id) => e.id == id) < 0)
const arr = ids.concat(
fliterData.reduce((p, c) => {
p.push(c.id)
return p
}, []),
)
localStorage.setItem('warning_id', arr.join(','))
fliterData.forEach((e, index) => {
openNotificationWithIcon(e.content, 4.5 + index * 0.5)
})
}
const openNotificationWithIcon = (message: string, duration = 4.5) => {
notification.warning({
message: '报警',
duration: duration,
class: 'warning-class',
style: {
backgroundColor: 'rgba(28, 44, 52, 0.8)',
color: '#fff',
},
getContainer: (): any => {
return document.body.querySelector(`.visualization—xx`)
},
description: message,
})
}
createVContext({
rootEmitter: vEmitter,
})
@ -130,13 +168,18 @@
const visibleMapModal = ref<boolean>(false)
onMounted(() => {
timer1 = setInterval(() => vEmitter.emit('interval:auto'), 1000 * 60)
timer1 = setInterval(() => {
getWarning()
vEmitter.emit('interval:auto')
}, 1000 * 60)
timer2 = setInterval(() => vEmitter.emit('interval:tab'), 1000 * 30)
timer3 = setInterval(() => vEmitter.emit('interval:tab1'), 1000 * 10)
new Build(cavsRef).run()
// Am(unref(cavsRef))
})
onBeforeMount(() => {
getWarning()
vEmitter.on('map:click', () => {
// visibleMapModal.value = true
})
@ -161,7 +204,15 @@
},
})
</script>
<style lang="less">
.warning-class {
border: 1px solid #396684;
.ant-notification-notice-message {
color: #fff;
}
}
</style>
<style scoped lang="less">
.bg-img {
// background-image: url('../../assets/images/map-bg.png') no-repeat;