Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	yarn.lock
This commit is contained in:
xieyonghong 2023-01-13 14:30:14 +08:00
commit 9332e59f51
26 changed files with 1106 additions and 190 deletions

View File

@ -30,7 +30,8 @@
"unplugin-auto-import": "^0.12.1",
"unplugin-vue-components": "^0.22.12",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
"vue-router": "^4.1.6",
"vue3-markdown-it": "^1.0.10"
},
"devDependencies": {
"@commitlint/cli": "^17.4.1",

View File

@ -1,4 +1,5 @@
import server from '@/utils/request'
import { DeviceInstance } from '@/views/device/instance/typings'
/**
*
@ -13,4 +14,11 @@ export const deleteMetadata = (deviceId: string) => server.remove(`/device-insta
* @param data
* @returns
*/
export const saveMetadata = (id: string, data: string) => server.put(`/device/instance/${id}/metadata`, data)
export const saveMetadata = (id: string, data: string) => server.put(`/device/instance/${id}/metadata`, data)
/**
* ID获取设备详情
* @param id ID
* @returns
*/
export const detail = (id: string) => server.get<DeviceInstance>(`/device-instance/${id}/detail`)

View File

@ -1,5 +1,5 @@
import server from '@/utils/request'
import type { DeviceMetadata } from '@/views/device/Product/typings'
import { DeviceMetadata, ProductItem } from '@/views/device/Product/typings'
/**
*
@ -23,4 +23,17 @@ export const convertMetadata = (direction: 'from' | 'to', type: string, data: an
* @param data
* @returns
*/
export const modify = (id: string, data: any) => server.put(`/device-product/${id}`, data)
export const modify = (id: string, data: any) => server.put(`/device-product/${id}`, data)
/**
*
* @returns
*/
export const getCodecs = () => server.get<{id: string, name: string}>('/device/product/metadata/codecs')
/**
* ID获取产品详情
* @param id ID
* @returns
*/
export const detail = (id: string) => server.get<ProductItem>(`/device-product/${id}`)

View File

@ -12,4 +12,6 @@ export const postInitSet = (data) => server.post(`/user/settings/init`, data)
export const systemVersion = () => server.get(`/system/version`)
export const bindInfo = () => server.get(`/application/sso/_all`)
export const bindInfo = () => server.get(`/application/sso/_all`)
export const settingDetail = (scopes) => server.get(`/system/config/${scopes}`)

12
src/api/notice/config.ts Normal file
View File

@ -0,0 +1,12 @@
import { patch, post, get } from '@/utils/request'
export default {
// 列表
list: (data: any) => post(`/notifier/config/_query`, data),
// 详情
detail: (id: string): any => get(`/notifier/config/${id}`),
// 新增
save: (data: any) => post(`/notifier/config`, data),
// 修改
update: (data: any) => patch(`/notifier/config`, data)
}

View File

@ -54,8 +54,8 @@
delete: item.key === 'delete',
}"
>
<!-- <slot name="actions" v-bind="item"></slot> -->
<a-popconfirm v-if="item.popConfirm" v-bind="item.popConfirm">
<slot name="actions" v-bind="item"></slot>
<!-- <a-popconfirm v-if="item.popConfirm" v-bind="item.popConfirm">
<a-button :disabled="item.disabled">
<DeleteOutlined v-if="item.key === 'delete'" />
<template v-else>
@ -72,7 +72,7 @@
<span>{{ item.text }}</span>
</template>
</a-button>
</template>
</template> -->
</div>
</div>
</slot>
@ -284,13 +284,14 @@ const handleClick = () => {
display: flex;
flex-grow: 1;
& > span,
button {
width: 100% !important;
border-radius: 0 !important;
& > :deep(span, button) {
width: 100%;
border-radius: 0;
}
button {
:deep(button) {
width: 100%;
border-radius: 0;
background: #f6f6f6;
border: 1px solid #e6e6e6;
color: #2f54eb;
@ -322,7 +323,7 @@ const handleClick = () => {
flex-basis: 60px;
flex-grow: 0;
button {
:deep(button) {
background: @error-color-deprecated-bg;
border: 1px solid @error-color-outline;
@ -348,7 +349,7 @@ const handleClick = () => {
}
}
button[disabled] {
:deep(button[disabled]) {
background: @disabled-bg;
border-color: @disabled-color;

View File

@ -11,16 +11,21 @@ enum ModelEnum {
CARD = 'CARD',
}
enum TypeEnum {
TREE = 'TREE',
PAGE = 'PAGE',
}
type RequestData = {
code: string;
result: {
data: Record<string, any>[] | undefined;
data?: Record<string, any>[] | undefined;
pageIndex: number;
pageSize: number;
total: number;
};
status: number;
} & Record<string, any>;
} | Record<string, any>;
export interface ActionsType {
key: string;
@ -39,16 +44,10 @@ export interface JColumnProps extends ColumnProps{
}
export interface JTableProps extends TableProps{
request?: (params: Record<string, any> & {
pageSize: number;
pageIndex: number;
}) => Promise<Partial<RequestData>>;
request?: (params?: Record<string, any>) => Promise<Partial<RequestData>>;
cardBodyClass?: string;
columns: JColumnProps[];
params?: Record<string, any> & {
pageSize: number;
pageIndex: number;
};
params?: Record<string, any>;
model?: keyof typeof ModelEnum | undefined; // 显示table还是card
// actions?: ActionsType[];
noPagination?: boolean;
@ -64,6 +63,8 @@ export interface JTableProps extends TableProps{
*/
gridColumns?: number[];
alertRender?: boolean;
type?: keyof typeof TypeEnum;
defaultParams?: Record<string, any>;
}
const JTable = defineComponent<JTableProps>({
@ -96,10 +97,6 @@ const JTable = defineComponent<JTableProps>({
type: [String, undefined],
default: undefined
},
// actions: {
// type: Array as PropType<ActionsType[]>,
// default: () => []
// },
noPagination: {
type: Boolean,
default: false
@ -127,6 +124,19 @@ const JTable = defineComponent<JTableProps>({
alertRender: {
type: Boolean,
default: true
},
type: {
type: String,
default: 'PAGE'
},
defaultParams: {
type: Object,
default: () => {
return {
pageIndex: 0,
pageSize: 12
}
}
}
} as any,
setup(props: JTableProps ,{ slots, emit }){
@ -162,25 +172,30 @@ const JTable = defineComponent<JTableProps>({
const handleSearch = async (_params?: Record<string, any>) => {
loading.value = true
if(props.request) {
const resp = await props.request({
pageSize: 12,
pageIndex: 1,
const resp = await props.request({
...props.defaultParams,
..._params
})
if(resp.status === 200){
// 判断如果是最后一页且最后一页为空,就跳转到前一页
if(resp.result?.data?.length === 0 && resp.result.total && resp.result.pageSize && resp.result.pageIndex) {
handleSearch({
..._params,
pageSize: pageSize.value,
pageIndex: pageIndex.value - 1,
})
if(props.type === 'PAGE'){
// 判断如果是最后一页且最后一页为空,就跳转到前一页
if(resp.result.total && resp.result.pageSize && resp.result.pageIndex && resp.result?.data?.length === 0) {
handleSearch({
..._params,
pageSize: pageSize.value,
pageIndex: pageIndex.value > 0 ? pageIndex.value - 1 : 0,
})
} else {
_dataSource.value = resp.result?.data || []
pageIndex.value = resp.result?.pageIndex || 0
pageSize.value = resp.result?.pageSize || 6
total.value = resp.result?.total || 0
}
} else {
_dataSource.value = resp.result?.data || []
pageIndex.value = resp.result?.pageIndex || 0
pageSize.value = resp.result?.pageSize || 6
total.value = resp.result?.total || 0
_dataSource.value = resp?.result || []
}
} else {
_dataSource.value = []
}
} else {
_dataSource.value = props?.dataSource || []
@ -282,7 +297,7 @@ const JTable = defineComponent<JTableProps>({
</div>
{/* 分页 */}
{
_dataSource.value.length && !props.noPagination &&
(!!_dataSource.value.length) && !props.noPagination && props.type === 'PAGE' &&
<div class={styles['jtable-pagination']}>
<Pagination
size="small"
@ -292,14 +307,16 @@ const JTable = defineComponent<JTableProps>({
current={pageIndex.value}
pageSize={pageSize.value}
pageSizeOptions={['12', '24', '48', '60', '100']}
showTotal={(total, range) => {
return `${range[0]} - ${range[1]} 条/总共 ${total}`
showTotal={(num) => {
const minSize = pageIndex.value * pageSize.value + 1;
const MaxSize = (pageIndex.value + 1) * pageSize.value;
return `${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num}`;
}}
onChange={(page, size) => {
handleSearch({
...props.params,
pageSize: size,
pageIndex: pageSize.value === size ? page : 1,
pageIndex: pageSize.value === size ? page : 0
})
}}
/>

View File

@ -1,7 +1,12 @@
import { InstanceModel } from "@/views/device/instance/typings";
import { DeviceInstance, InstanceModel } from "@/views/device/instance/typings";
import { defineStore } from "pinia";
export const useInstanceStore = defineStore({
id: 'device',
state: () => ({} as InstanceModel),
});
actions: {
setCurrent(current: Partial<DeviceInstance>) {
this.current = current
}
}
})

24
src/store/menu.ts Normal file
View File

@ -0,0 +1,24 @@
import { defineStore } from "pinia";
export const useMenuStore = defineStore({
id: 'menu',
state: () => ({
menus: {} as {[key: string]: string},
}),
getters: {
hasPermission(state) {
return (menuCode: string | string[]) => {
if (!menuCode) {
return true
}
if (!!Object.keys(state.menus).length) {
if (typeof menuCode === 'string') {
return !!this.menus[menuCode]
}
return menuCode.some(code => !!this.menus[code])
}
return false
}
}
}
})

14
src/store/product.ts Normal file
View File

@ -0,0 +1,14 @@
import { ProductItem } from "@/views/device/Product/typings";
import { defineStore } from "pinia";
export const useProductStore = defineStore({
id: 'product',
state: () => ({
current: {} as ProductItem | undefined
}),
actions: {
setCurrent(current: ProductItem) {
this.current = current
}
}
})

View File

@ -29,38 +29,21 @@ export const StatusColorEnum = {
'default': 'default',
}
class SystemConst {
static API_BASE = 'api';
static SYSTEM_NAME = 'Jetlinks';
static LOGIN = 'LOGIN-STATUS';
static DOC_URL = 'http://doc.jetlinks.cn';
static BASE_CURD_MODAL_VISIBLE = 'BASE_CURD_MODAL_VISIBLE';
static BASE_CURD_CURRENT = 'BASE_CURD_CURRENT';
static BASE_CURD_MODEL = 'BASE_CURD_MODEL';
static BASE_UPDATE_DATA = 'BASE_UPDATE_DATA';
static GLOBAL_WEBSOCKET = 'GLOBAL-WEBSOCKET';
static BIND_USER_STATE = 'false';
static REFRESH_METADATA = 'refresh_metadata';
static REFRESH_METADATA_TABLE = 'refresh_metadata_table';
static GET_METADATA = 'get_metadata';
static REFRESH_DEVICE = 'refresh_device';
static AMAP_KEY = 'amap_key';
static Version_Code = 'version_code';
export const SystemConst = {
API_BASE: 'api',
SYSTEM_NAME: 'Jetlinks',
LOGIN: 'LOGIN-STATUS',
DOC_URL: 'http://doc.jetlinks.cn',
BASE_CURD_MODAL_VISIBLE: 'BASE_CURD_MODAL_VISIBLE',
BASE_CURD_CURRENT: 'BASE_CURD_CURRENT',
BASE_CURD_MODEL: 'BASE_CURD_MODEL',
BASE_UPDATE_DATA: 'BASE_UPDATE_DATA',
GLOBAL_WEBSOCKET: 'GLOBAL-WEBSOCKET',
BIND_USER_STATE: 'false',
REFRESH_METADATA: 'refresh_metadata',
REFRESH_METADATA_TABLE: 'refresh_metadata_table',
GET_METADATA: 'get_metadata',
REFRESH_DEVICE: 'refresh_device',
AMAP_KEY: 'amap_key',
VERSION_CODE: 'version_code',
}
export default SystemConst;

21
src/utils/utils.ts Normal file
View File

@ -0,0 +1,21 @@
/**
* JSON
* @param record
* @param fileName
*/
export const downloadObject = (record: Record<string, any>, fileName: string, format?: string) => {
// 创建隐藏的可下载链接
const ghostLink = document.createElement('a');
ghostLink.download = `${fileName ? '' : record?.name}${fileName}_${moment(new Date()).format(
format || 'YYYY_MM_DD',
)}.json`;
ghostLink.style.display = 'none';
//字符串内容转成Blob地址
const blob = new Blob([JSON.stringify(record)]);
ghostLink.href = URL.createObjectURL(blob);
//触发点击
document.body.appendChild(ghostLink);
ghostLink.click();
//移除
document.body.removeChild(ghostLink);
};

View File

@ -10,7 +10,7 @@
@cancelSelect="cancelSelect"
>
<template #headerTitle>
<a-button type="primary">新增</a-button>
<a-button type="primary" @click="add">新增</a-button>
</template>
<template #card="slotProps">
<CardBox
@ -37,7 +37,7 @@
</a-col>
</a-row>
</template>
<!-- <template #actions="item">
<template #actions="item">
<a-popconfirm v-if="item.popConfirm" v-bind="item.popConfirm">
<a-button :disabled="item.disabled">
<DeleteOutlined v-if="item.key === 'delete'" />
@ -56,7 +56,7 @@
</template>
</a-button>
</template>
</template> -->
</template>
</CardBox>
</template>
<template #id="slotProps">
@ -83,8 +83,10 @@ import server from "@/utils/request";
import type { ActionsType } from '@/components/Table/index.vue'
import { getImage } from '@/utils/comm';
import { DeleteOutlined } from '@ant-design/icons-vue'
import { message } from "ant-design-vue";
const request = (data: any) => server.post(`/device-product/_query`, data)
// const request = (data: any) => server.post(`/device/category/_tree`, {paging: false})
const columns = [
{
@ -152,26 +154,26 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
tooltip: {
title: '导入'
},
disabled: true,
icon: 'icon-xiazai'
},
{
key: 'delete',
// disabled: true,
text: "删除",
disabled: !!data?.state,
tooltip: {
title: !!data?.state ? '正常的产品不能删除' : '删除'
},
// popConfirm: {
// title: '?'
// },
popConfirm: {
title: '确认删除?'
},
icon: 'icon-huishouzhan'
}
]
}
const p = h('p', 'hi')
const add = () => {
message.warn('123')
}
</script>

View File

@ -0,0 +1,158 @@
<template>
<a-drawer :mask-closable="false" title="查看物模型" width="700" v-model:visible="_visible" destroy-on-close @close="close">
<template #extra>
<a-space>
<a-button type="primary" @click="handleExport">
导出
</a-button>
</a-space>
</template>
<a-spin :spinning="loading">
<div class="cat-content">
<p class="cat-tip">
物模型是对设备在云端的功能描述包括设备的属性服务和事件物联网平台通过定义一种物的描述语言来描述物模型称之为
TSL Thing Specification Language采用 JSON 格式您可以根据 TSL
组装上报设备的数据您可以导出完整物模型用于云端应用开发
</p>
</div>
<a-tabs @change="handleConvertMetadata">
<a-tab-pane v-for="item in codecs" :tab-key="item.id" :key="item.id">
<div class="cat-panel">
<!-- TODO 代码编辑器 -->
</div>
</a-tab-pane>
</a-tabs>
</a-spin>
</a-drawer>
</template>
<script setup lang="ts" name="Cat">
import { message } from 'ant-design-vue/es';
import { downloadObject } from '@/utils/utils'
import { useInstanceStore } from '@/store/instance';
import { useProductStore } from '@/store/product';
import type { Key } from 'ant-design-vue/es/_util/type';
import { convertMetadata, getCodecs, detail as productDetail } from '@/api/device/product';
import { detail } from '@/api/device/instance'
interface Props {
visible: boolean;
type: 'product' | 'device';
}
interface Emits {
(e: 'update:visible', data: boolean): void;
}
const props = defineProps<Props>()
const emits = defineEmits<Emits>()
const route = useRoute()
const loading = ref(false)
const _visible = computed({
get: () => {
return props.visible;
},
set: (val: any) => {
emits('update:visible', val);
},
})
const close = () => {
emits('update:visible', false);
}
const instanceStore = useInstanceStore()
const productStore = useProductStore()
const metadataMap = {
product: productStore.current?.metadata as string,
device: instanceStore.current?.metadata as string,
};
const metadata = metadataMap[props.type];
const value = ref(metadata)
const handleExport = async () => {
try {
downloadObject(
JSON.parse(value.value),
`${props.type === 'device'
? instanceStore.current?.name
: productStore.current?.name
}-物模型`,
'YYYY/MM/DD',
);
} catch (e) {
message.error('请先配置物模型');
}
}
const handleConvertMetadata = (key: Key) => {
if (key === 'alink') {
value.value = '';
if (metadata) {
convertMetadata('to', 'alink', JSON.parse(metadata)).then(res => {
if (res.status === 200) {
value.value = JSON.stringify(res.result)
}
});
}
} else {
value.value = metadata;
}
};
const codecs = ref<{ id: string; name: string }[]>()
const routeChange = async (id: string) => {
const res = await getCodecs()
if (res.status === 200) {
codecs.value = [{ id: 'jetlinks', name: 'jetlinks' }].concat(res.result)
}
if (props.type === 'device' && id) {
detail(id as string).then((resp) => {
if (resp.status === 200) {
instanceStore.setCurrent(resp.result);
const _metadata = resp.result?.metadata;
value.value = _metadata;
}
});
}
}
watch(
() => route.params.id,
(id) => routeChange(id as string),
{ immediate: true }
)
watchEffect(() => {
if (props.visible) {
loading.value = true
const { id } = route.params
if (props.type === 'device') {
detail(id as string).then((resp) => {
loading.value = false
instanceStore.setCurrent(resp.result)
value.value = resp.result.metadata
});
} else {
productDetail(id as string).then((resp) => {
loading.value = false
// productStore.setCurrent(resp.result)
value.value = resp.result.metadata
});
}
}
})
</script>
<style scoped lang="scss">
.cat-content {
background: #F6F6F6;
.cat-tip {
padding: 10px;
color: rgba(0, 0, 0, 0.55);
}
}
.cat-panel {
border: 1px solid #eeeeee;
height: 670px;
width: 650px;
}
</style>

View File

@ -0,0 +1,262 @@
<template>
<a-modal :mask-closable="false" title="导入物模型" destroy-on-close v-model:visible="_visible" @cancel="close"
@ok="handleImport" :confirm-loading="loading">
<div class="import-content">
<p class="import-tip">
<exclamation-circle-outlined style="margin-right: 5px" />
导入的物模型会覆盖原来的属性功能事件标签请谨慎操作
</p>
</div>
<a-form layout="vertical" v-model="formModel">
<a-form-item label="导入方式" v-bind="validateInfos.type">
<a-select v-if="type === 'product'" v-model:value="formModel.type">
<a-select-option value="copy">拷贝产品</a-select-option>
<a-select-option value="import">导入物模型</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="选择产品" v-bind="validateInfos.copy" v-if="formModel.type === 'copy'">
<a-select :options="productList" v-model:value="formModel.copy" option-filter-prop="label"></a-select>
</a-form-item>
<a-form-item label="物模型类型" v-bind="validateInfos.metadata"
v-if="type === 'device' || formModel.type === 'import'">
<a-select v-model:value="formModel.metadata">
<a-select-option value="jetlinks">Jetlinks物模型</a-select-option>
<a-select-option value="alink">阿里云物模型TSL</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="导入类型" v-bind="validateInfos.metadataType"
v-if="type === 'device' || formModel.type === 'import'">
<a-select v-model:value="formModel.metadataType">
<a-select-option value="file">文件上传</a-select-option>
<a-select-option value="script">脚本</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="文件上传" v-bind="validateInfos.upload" v-if="formModel.metadataType === 'file'">
<a-upload v-model:file-list="formModel.upload" name="files" :before-upload="beforeUpload" accept=".json"
:show-upload-list="false"></a-upload>
</a-form-item>
<a-form-item label="物模型" v-bind="validateInfos.import" v-if="formModel.metadataType === 'script'">
<!-- TODO代码编辑器 -->
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts" name="Import">
import { useForm } from 'ant-design-vue/es/form';
import { saveMetadata } from '@/api/device/instance'
import { queryNoPagingPost, convertMetadata, modify } from '@/api/device/product'
import type { DefaultOptionType } from 'ant-design-vue/es/select';
import { UploadProps } from 'ant-design-vue/es';
import type { DeviceMetadata } from '@/views/device/Product/typings'
import { message } from 'ant-design-vue/es';
import { Store } from 'jetlinks-store';
import { SystemConst } from '@/utils/consts';
import { useInstanceStore } from '@/store/instance'
const route = useRoute()
const instanceStore = useInstanceStore()
interface Props {
visible: boolean,
type: 'device' | 'product',
}
interface Emits {
(e: 'update:visible', data: boolean): void;
}
const props = defineProps<Props>()
const emits = defineEmits<Emits>()
const loading = ref(false)
const _visible = computed({
get: () => {
return props.visible;
},
set: (val: any) => {
emits('update:visible', val);
},
})
const close = () => {
emits('update:visible', false);
}
/** form表单 */
const formModel = reactive<Record<string, any>>({
type: 'import',
metadata: 'jetlinks',
metadataType: 'script',
})
const rules = reactive({
type: [
{
required: true,
message: '请选择导入方式',
},
],
copy: [
{
required: true,
message: '请选择产品',
},
],
metadata: [
{
required: true,
message: '请选择物模型类型',
},
],
metadataType: [
{
required: true,
message: '请选择导入类型',
},
],
upload: [
{
required: true,
message: '请上传文件',
},
],
import: [
{
required: true,
message: '请输入物模型',
},
],
})
const { validate, validateInfos } = useForm(formModel, rules);
const onSubmit = () => {
validate().then(() => {
})
}
const productList = ref<DefaultOptionType[]>([])
const loadData = async () => {
const { id } = route.params || {}
const product = await queryNoPagingPost({
paging: false,
sorts: [{ name: 'createTime', order: 'desc' }],
terms: [{ column: 'id$not', value: id }],
}) as any
productList.value = product.result.filter((i: any) => i?.metadata).map((item: any) => ({
label: item.name,
value: item.metadata,
key: item.id
})) as DefaultOptionType[]
}
loadData()
const beforeUpload: UploadProps['beforeUpload'] = file => {
const reader = new FileReader();
reader.readAsText(file);
reader.onload = (json) => {
formModel.import = json.target?.result;
};
}
const operateLimits = (mdata: DeviceMetadata) => {
const obj: DeviceMetadata = { ...mdata };
const old = JSON.parse(instanceStore.detail?.metadata || '{}');
const fid = instanceStore.detail?.features?.map(item => item.id);
if (fid?.includes('eventNotModifiable')) {
obj.events = old?.events || [];
}
if (fid?.includes('propertyNotModifiable')) {
obj.properties = old?.properties || [];
}
(obj?.events || []).map((item, index) => {
return { ...item, sortsIndex: index };
});
(obj?.properties || []).map((item, index) => {
return { ...item, sortsIndex: index };
});
(obj?.functions || []).map((item, index) => {
return { ...item, sortsIndex: index };
});
(obj?.tags || []).map((item, index) => {
return { ...item, sortsIndex: index };
});
return obj;
};
const handleImport = async () => {
validate().then(async (data) => {
loading.value = true
if (data.metadata === 'alink') {
const res = await convertMetadata('from', 'alink', data.import)
if (res.status === 200) {
const metadata = JSON.stringify(operateLimits(res.result))
const { id } = route.params || {}
if (props?.type === 'device') {
await saveMetadata(id as string, metadata)
} else {
await modify(id as string, { metadata: metadata })
}
loading.value = false
// MetadataAction.insert(JSON.parse(metadata || '{}'));
message.success('导入成功')
} else {
loading.value = false
message.error('发生错误!')
}
Store.set(SystemConst.GET_METADATA, true)
Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
close()
} else {
try {
const _object = JSON.parse(data[props?.type === 'device' ? 'import' : data?.type] || '{}')
if (
!(!!_object?.properties || !!_object?.events || !!_object?.functions || !!_object?.tags)
) {
message.error('物模型数据不正确')
loading.value = false;
return;
}
const { id } = route.params || {}
const params = {
id,
metadata: JSON.stringify(operateLimits(_object as DeviceMetadata)),
};
const paramsDevice = JSON.stringify(operateLimits(_object as DeviceMetadata))
let resp = undefined
if (props?.type === 'device') {
resp = await saveMetadata(id as string, paramsDevice)
} else {
resp = await modify(id as string, params)
}
loading.value = false
if (resp.status === 200) {
if (props?.type === 'device') {
const metadata: DeviceMetadata = JSON.parse(paramsDevice || '{}')
// MetadataAction.insert(metadata);
message.success('导入成功')
} else {
const metadata: DeviceMetadata = JSON.parse(params?.metadata || '{}')
// MetadataAction.insert(metadata);
message.success('导入成功')
}
}
Store.set(SystemConst.GET_METADATA, true)
Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
close();
} catch (e) {
loading.value = false
message.error(e === 'error' ? '物模型数据不正确' : '上传json格式的物模型文件')
}
}
})
}
// const showProduct = computed(() => formModel.type === 'copy')
</script>
<style scoped lang="scss">
.import-content {
background: rgb(236, 237, 238);
.import-tip {
padding: 10px;
}
}
</style>

View File

@ -0,0 +1,101 @@
<template>
<div class='device-detail-metadata' style="position: relative;">
<div class="tips" style="width: 40%">
<a-tooltip :title="instanceStore.detail?.independentMetadata && type === 'device'
? '该设备已脱离产品物模型,修改产品物模型对该设备无影响'
: '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'">
<div class="ellipsis">
<info-circle-outlined style="margin-right: 3px" />
{{
instanceStore.detail?.independentMetadata && type === 'device'
? '该设备已脱离产品物模型,修改产品物模型对该设备无影响'
: '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'
}}
</div>
</a-tooltip>
</div>
<a-tabs class="metadataNav" destroyInactiveTabPane>
<template #rightExtra>
<a-space>
<PermissionButton v-if="type === 'device'" :hasPermission="`${permission}:update`"
:popConfirm="{ title: '确认重置?', onConfirm: resetMetadata, }" :tooltip="{ title: '重置后将使用产品的物模型配置' }"
key="reload">
重置操作
</PermissionButton>
<PermissionButton :isPermission="`${permission}:update`" @click="visible = true">快速导入</PermissionButton>
<PermissionButton :isPermission="`${permission}:update`" @click="cat = true">物模型TSL</PermissionButton>
</a-space>
</template>
<a-tab-pane tab="属性定义" key="properties">
<BaseMetadata target={props.type} type="properties" :permission="permission" />
</a-tab-pane>
<a-tab-pane tab="功能定义" key="functions">
<BaseMetadata target={props.type} type="functions" :permission="permission" />
</a-tab-pane>
<a-tab-pane tab="事件定义" key="events">
<BaseMetadata target={props.type} type="events" :permission="permission" />
</a-tab-pane>
<a-tab-pane tab="标签定义" key="tags">
<BaseMetadata target={props.type} type="tags" :permission="permission" />
</a-tab-pane>
</a-tabs>
<Import :visible="visible" :type="type" @close="visible = false" />
<Cat :visible="cat" @close="cat = false" :type="type" />
</div>
</template>
<script setup lang="ts" name="Metadata">
import PermissionButton from '@/components/PermissionButton/index.vue'
import { deleteMetadata } from '@/api/device/instance.js'
import { message } from 'ant-design-vue'
import { Store } from 'jetlinks-store'
import { SystemConst } from '@/utils/consts'
import { useInstanceStore } from '@/store/instance'
import Import from './Import/index.vue'
import Cat from './Cat/index.vue'
const route = useRoute()
const instanceStore = useInstanceStore()
interface Props {
type: 'product' | 'device';
independentMetadata?: boolean;
}
const props = defineProps<Props>()
const permission = computed(() => props.type === 'device' ? 'device/Instance' : 'device/Product')
const visible = ref(false)
const cat = ref(false)
//
const resetMetadata = async () => {
const { id } = route.params
const resp = await deleteMetadata(id as string)
if (resp.status === 200) {
message.info('操作成功')
Store.set(SystemConst.REFRESH_DEVICE, true)
setTimeout(() => {
Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
}, 400)
}
}
</script>
<style scoped lang="scss">
.device-detail-metadata {
.tips {
position: absolute;
top: 12px;
z-index: 1;
margin-left: 330px;
font-weight: 100;
}
.metadataNav {
:global {
.ant-card-body {
padding: 0;
}
}
}
}
</style>

View File

@ -189,7 +189,8 @@
class="config-right-item-context"
v-if="config.document"
>
{{ config.document }}
<Markdown :source="config.document" />
</div>
</div>
<div
@ -314,6 +315,8 @@ import {
import AccessCard from './AccessCard/index.vue';
import { message, Form } from 'ant-design-vue';
import type { FormInstance } from 'ant-design-vue';
import Markdown from 'vue3-markdown-it';
//1
const resultList1 = [

View File

@ -1,65 +1,63 @@
<!-- webhook请求头可编辑表格 -->
<template>
<a-table
:columns="columns"
:data-source="dataSource"
bordered
:pagination="false"
>
<template #bodyCell="{ column, text, record }">
<template v-if="['KEY', 'VALUE'].includes(column.dataIndex)">
<a-input v-model="record[column.dataIndex]" />
<div class="table-wrapper">
<a-table
:columns="columns"
:data-source="dataSource"
bordered
:pagination="false"
>
<template #bodyCell="{ column, text, record }">
<template v-if="['key', 'value'].includes(column.dataIndex)">
<a-input v-model:value="record[column.dataIndex]" />
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-button type="text">
<template #icon>
<delete-outlined @click="handleDelete(record.id)" />
</template>
</a-button>
</template>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-button type="text">
<template #icon>
<delete-outlined @click="handleDelete(record.idx)" />
</template>
</a-button>
</a-table>
<a-button
type="dashed"
@click="handleAdd"
style="width: 100%; margin-top: 5px"
>
<template #icon>
<plus-outlined />
</template>
</template>
</a-table>
<a-button
type="dashed"
@click="handleAdd"
style="width: 100%; margin-top: 5px"
>
<template #icon>
<plus-outlined />
</template>
添加
</a-button>
添加
</a-button>
</div>
</template>
<script setup lang="ts">
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
// import { cloneDeep } from 'lodash-es';
// import { defineComponent, reactive, ref } from 'vue';
// import type { UnwrapRef } from 'vue';
import { PropType } from 'vue';
import { IHeaders } from '../../types';
interface DataItem {
idx: number;
KEY: string;
VALUE: string;
}
type Emits = {
(e: 'update:headers', data: IHeaders[]): void;
};
const emit = defineEmits<Emits>();
const data: DataItem[] = [];
for (let i = 0; i < 2; i++) {
data.push({
idx: i,
KEY: `key ${i}`,
VALUE: `value${i}`,
});
}
const props = defineProps({
headers: {
type: Array as PropType<IHeaders[]>,
default: () => [],
},
});
const columns = [
{
title: 'KEY',
dataIndex: 'KEY',
dataIndex: 'key',
},
{
title: 'VALUE',
dataIndex: 'VALUE',
dataIndex: 'value',
},
{
title: '操作',
@ -69,17 +67,20 @@ const columns = [
},
];
const dataSource = ref(data);
console.log('dataSource: ', dataSource.value);
const dataSource = computed({
get: () => props.headers,
set: (val) => emit('update:headers', val),
});
const handleDelete = (idx: number) => {
const handleDelete = (id: number) => {
const idx = dataSource.value.findIndex((f) => f.id === id);
dataSource.value.splice(idx, 1);
};
const handleAdd = () => {
dataSource.value.push({
idx: dataSource.value.length + 1,
KEY: `key ${dataSource.value.length + 1}`,
VALUE: `value ${dataSource.value.length + 1}`,
id: dataSource.value.length,
key: '',
value: '',
});
};
</script>

View File

@ -0,0 +1,50 @@
import { Image } from 'ant-design-vue';
import './index.less';
import { getImage } from '@/utils/comm';
const DingTalk = () => {
const appKey = getImage(
'/notice/doc/config/dingTalk-message/01-AppKey.jpg',
);
const appSecret = getImage(
'/notice/doc/config/dingTalk-message/02-AppSecret.jpg',
);
return (
<div class={'doc'}>
<div class={'url'}>
<a
href="https://open-dev.dingtalk.com"
target="_blank"
rel="noopener noreferrer"
>
https://open-dev.dingtalk.com
</a>
</div>
<h1>1. </h1>
<div>
</div>
<h1>2.</h1>
<div>
<h2>1AppKey</h2>
<div>
AppKey和AppSecret
</div>
<div>----</div>
<div class={'image'}>
<Image width="100%" src={appKey} />
</div>
</div>
<h2>2AppSecret</h2>
<div>
<div></div>
<div>----</div>
<div class={'image'}>
<Image width="100%" src={appSecret} />
</div>
</div>
</div>
);
};
export default DingTalk;

View File

@ -0,0 +1,35 @@
.doc {
height: 750px;
padding: 24px;
overflow-y: auto;
color: rgba(#000, 0.8);
font-size: 14px;
background-color: #fafafa;
.url {
padding: 8px 16px;
color: #2f54eb;
background-color: rgba(#a7bdf7, 0.2);
}
h1 {
margin: 16px 0;
color: rgba(#000, 0.85);
font-weight: bold;
font-size: 14px;
&:first-child {
margin-top: 0;
}
}
h2 {
margin: 6px 0;
color: rgba(0, 0, 0, 0.8);
font-size: 14px;
}
.image {
margin: 16px 0;
}
}

View File

@ -0,0 +1,6 @@
import DingTalk from './DingTalk';
const Doc = () => {
return <DingTalk />;
};
export default Doc;

View File

@ -259,6 +259,7 @@
<a-button
type="primary"
@click="handleSubmit"
:loading="btnLoading"
style="width: 100%"
>
保存
@ -266,14 +267,16 @@
</a-form-item>
</a-form>
</a-col>
<a-col :span="12" :push="2"></a-col>
<a-col :span="12" :push="2">
<Doc />
</a-col>
</a-row>
</a-card>
</div>
</template>
<script setup lang="ts">
import { getImage, LocalStore } from '@/utils/comm';
import { getImage } from '@/utils/comm';
import { Form } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { ConfigFormData } from '../types';
@ -283,8 +286,12 @@ import {
MSG_TYPE,
} from '@/views/notice/const';
import regionList from './regionId';
import EditTable from './components/EditTable.vue'
import EditTable from './components/EditTable.vue';
import configApi from '@/api/notice/config';
import Doc from './doc/index';
const router = useRouter();
const route = useRoute();
const useForm = Form.useForm;
//
@ -311,9 +318,8 @@ const formData = ref<ConfigFormData>({
description: '',
name: '',
provider: 'dingTalkMessage',
type: NOTICE_METHOD[0].value,
type: 'dingTalk',
});
//
watch(
() => formData.value.type,
@ -383,25 +389,56 @@ const formRules = ref({
pattern:
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/,
message: 'Webhook需要是一个合法的URL',
trigger: 'blur',
},
],
description: [{ max: 200, message: '最多可输入200个字符' }],
});
const { resetFields, validate, validateInfos } = useForm(
const { resetFields, validate, validateInfos, clearValidate } = useForm(
formData.value,
formRules.value,
);
console.log('validateInfos: ', validateInfos);
watch(
() => formData.value.type,
() => {
clearValidate();
},
{ deep: true },
);
const getDetail = async () => {
const res = await configApi.detail(route.params.id as string);
console.log('res: ', res);
formData.value = res.result;
console.log('formData.value: ', formData.value);
};
getDetail();
/**
* 表单提交
*/
const btnLoading = ref<boolean>(false);
const handleSubmit = () => {
validate()
.then(async () => {})
.catch((err) => {});
.then(async () => {
// console.log('formData.value: ', formData.value);
btnLoading.value = true;
let res;
if (!formData.value.id) {
res = await configApi.save(formData.value);
} else {
res = await configApi.update(formData.value);
}
// console.log('res: ', res);
if (res?.success) {
message.success('保存成功');
router.back();
}
btnLoading.value = false;
})
.catch((err) => {
console.log('err: ', err);
});
};
</script>

View File

@ -3,6 +3,20 @@
<div class="page-container">通知配置</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import configApi from '@/api/notice/config';
const getList = async () => {
const res = await configApi.list({
current: 1,
pageIndex: 0,
pageSize: 12,
sorts: [{ name: 'createTime', order: 'desc' }],
terms: [],
});
console.log('res: ', res);
};
getList();
</script>
<style lang="less" scoped></style>

View File

@ -1,4 +1,5 @@
interface IHeaders {
export interface IHeaders {
id?: number;
key: string;
value: string;
}
@ -34,4 +35,8 @@ export type ConfigFormData = {
name: string;
provider: string;
type: string;
id?: string;
maxRetryTimes?: number;
creatorId?: string;
createTime?: number;
};

View File

@ -5,8 +5,16 @@
<div class="left">
<img
style="width: 100%; height: 100%"
:src="getImage('/login.png')"
:src="basis.backgroud || getImage('/login.png')"
/>
<a
href="https://beian.miit.gov.cn/#/Integrated/index"
target="_blank"
rel="noopener noreferrer"
class="records"
>
备案渝ICP备19017719号-1
</a>
</div>
<div class="right">
<div class="content">
@ -16,11 +24,13 @@
<img
alt="logo"
class="logo"
:src="getImage('/logo.png')"
:src="basis.logo || getImage('/logo.png')"
/>
<!-- </link> -->
</div>
<div class="desc">物联网平台</div>
<div class="desc">
{{ basis.title || SystemConst.SYSTEM_NAME }}
</div>
<div class="main">
<a-form
layout="vertical"
@ -168,11 +178,13 @@ import {
getInitSet,
systemVersion,
bindInfo,
settingDetail,
} from '@/api/login';
import Cookies from 'js-cookie';
import { useUserInfo } from '@/store/userInfo';
import { LocalStore } from '@/utils/comm';
import { BASE_API_PATH, TOKEN_KEY, Version_Code } from '@/utils/variable';
import { SystemConst } from '@/utils/consts';
const store = useUserInfo();
const router = useRouter();
@ -200,6 +212,7 @@ const codeConfig = ref(false);
const loading = ref(false);
const bindings = ref<any[]>();
const basis = ref<any>({});
const defaultImg = getImage('/apply/provider1.png');
const iconMap = new Map();
@ -222,6 +235,15 @@ const onFinish = async () => {
username: form.username,
});
LocalStore.set(TOKEN_KEY, res?.result.token);
// if (res.result.username === 'admin') {
// const resp: any = await getInitSet();
// if (resp.status === 200 && !resp.result.length) {
// window.location.href = '/#/init-home';
// return;
// }
// }
// window.location.href = '/';
const resp: any = await getInitSet();
if (resp.success) {
router.push('/demo');
@ -257,6 +279,7 @@ const getCookie = () => {
};
const getOpen = () => {
LocalStore.removeAll();
systemVersion().then((res: any) => {
if (res.success && res.result) {
LocalStore.set(Version_Code, res.result.edition);
@ -269,6 +292,18 @@ const getOpen = () => {
}
}
});
settingDetail('front').then((res) => {
if (res.status === 200) {
const ico: any = document.querySelector('link[rel="icon"]');
ico.href = res.result.ico;
basis.value = res.result;
if (res.result.title) {
document.title = res.result.title;
} else {
document.title = '';
}
}
});
};
const handleClickOther = (item: any) => {
@ -316,6 +351,14 @@ screenRotation(screenWidth.value, screenHeight.value);
.left {
width: 73%;
height: 100%;
.records {
position: absolute;
top: 96%;
left: 35%;
color: rgba(0, 0, 0, 0.35);
font-size: 14px;
}
}
.right {

158
yarn.lock
View File

@ -796,11 +796,6 @@
dependencies:
"@types/webxr" "*"
"@types/web-bluetooth@^0.0.16":
version "0.0.16"
resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==
"@types/webxr@*":
version "0.5.0"
resolved "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz"
@ -1008,28 +1003,6 @@
three "0.143.0"
uppercamelcase "^1.1.0"
"@vueuse/core@^9.10.0":
version "9.10.0"
resolved "https://registry.npmmirror.com/@vueuse/core/-/core-9.10.0.tgz#2ef6e55ca773c5b2db1e3f13b8292af96dd32214"
integrity sha512-CxMewME07qeuzuT/AOIQGv0EhhDoojniqU6pC3F8m5VC76L47UT18DcX88kWlP3I7d3qMJ4u/PD8iSRsy3bmNA==
dependencies:
"@types/web-bluetooth" "^0.0.16"
"@vueuse/metadata" "9.10.0"
"@vueuse/shared" "9.10.0"
vue-demi "*"
"@vueuse/metadata@9.10.0":
version "9.10.0"
resolved "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.10.0.tgz#1a5eb94ca755bd8e666505f47da7d88969cffdc7"
integrity sha512-G5VZhgTCapzU9rv0Iq2HBrVOSGzOKb+OE668NxhXNcTjUjwYxULkEhAw70FtRLMZc+hxcFAzDZlKYA0xcwNMuw==
"@vueuse/shared@9.10.0":
version "9.10.0"
resolved "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.10.0.tgz#49874a0f9955d28689b3133de660367c63dbc030"
integrity sha512-vakHJ2ZRklAzqmcVBL38RS7BxdBA4+5poG9NsSyqJxrt9kz0zX3P5CXMy0Hm6LFbZXUgvKdqAS3pUH1zX/5qTQ==
dependencies:
vue-demi "*"
JSONStream@^1.0.4:
version "1.3.5"
resolved "https://registry.npmmirror.com/JSONStream/-/JSONStream-1.3.5.tgz"
@ -1040,17 +1013,17 @@ JSONStream@^1.0.4:
acorn-walk@^8.1.1:
version "8.2.0"
resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.2.0.tgz"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@^8.4.1, acorn@^8.5.0, acorn@^8.8.1:
version "8.8.1"
resolved "https://registry.npmmirror.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz"
integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
resolved "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz"
integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
dependencies:
clean-stack "^2.0.0"
@ -2055,6 +2028,11 @@ entities@^2.0.0:
resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
entities@~2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
errno@^0.1.1:
version "0.1.8"
resolved "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz"
@ -2525,6 +2503,11 @@ header-case@^2.0.4:
capital-case "^1.0.4"
tslib "^2.0.3"
highlight.js@^11.3.1:
version "11.7.0"
resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e"
integrity sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==
homedir-polyfill@^1.0.0:
version "1.0.3"
resolved "https://registry.npmmirror.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz"
@ -3028,6 +3011,13 @@ lines-and-columns@^1.1.6:
resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
linkify-it@^3.0.1:
version "3.0.3"
resolved "https://registry.npmmirror.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e"
integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==
dependencies:
uc.micro "^1.0.1"
lint-staged@^13.1.0:
version "13.1.0"
resolved "https://registry.npmmirror.com/lint-staged/-/lint-staged-13.1.0.tgz"
@ -3103,6 +3093,11 @@ lodash.camelcase@^4.3.0:
resolved "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz"
integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
lodash.flow@^3.5.0:
version "3.5.0"
resolved "https://registry.npmmirror.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
integrity sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==
lodash.isfunction@^3.0.9:
version "3.0.9"
resolved "https://registry.npmmirror.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz"
@ -3265,6 +3260,85 @@ map-obj@^4.0.0:
resolved "https://registry.npmmirror.com/map-obj/-/map-obj-4.3.0.tgz"
integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==
markdown-it-abbr@^1.0.4:
version "1.0.4"
resolved "https://registry.npmmirror.com/markdown-it-abbr/-/markdown-it-abbr-1.0.4.tgz#d66b5364521cbb3dd8aa59dadfba2fb6865c8fd8"
integrity sha512-ZeA4Z4SaBbYysZap5iZcxKmlPL6bYA8grqhzJIHB1ikn7njnzaP8uwbtuXc4YXD5LicI4/2Xmc0VwmSiFV04gg==
markdown-it-anchor@^8.4.1:
version "8.6.6"
resolved "https://registry.npmmirror.com/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz#4a12e358c9c2167ee28cb7a5f10e29d6f1ffd7ca"
integrity sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==
markdown-it-deflist@^2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/markdown-it-deflist/-/markdown-it-deflist-2.1.0.tgz#50d7a56b9544cd81252f7623bd785e28a8dcef5c"
integrity sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg==
markdown-it-emoji@^2.0.0:
version "2.0.2"
resolved "https://registry.npmmirror.com/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz#cd42421c2fda1537d9cc12b9923f5c8aeb9029c8"
integrity sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==
markdown-it-footnote@^3.0.3:
version "3.0.3"
resolved "https://registry.npmmirror.com/markdown-it-footnote/-/markdown-it-footnote-3.0.3.tgz#e0e4c0d67390a4c5f0c75f73be605c7c190ca4d8"
integrity sha512-YZMSuCGVZAjzKMn+xqIco9d1cLGxbELHZ9do/TSYVzraooV8ypsppKNmUJ0fVH5ljkCInQAtFpm8Rb3eXSrt5w==
markdown-it-highlightjs@^3.6.0:
version "3.6.0"
resolved "https://registry.npmmirror.com/markdown-it-highlightjs/-/markdown-it-highlightjs-3.6.0.tgz#b567408c633d71e5e4cc33e1d0121a44447d2f29"
integrity sha512-ex+Lq3cVkprh0GpGwFyc53A/rqY6GGzopPCG1xMsf8Ya3XtGC8Uw9tChN1rWbpyDae7tBBhVHVcMM29h4Btamw==
dependencies:
highlight.js "^11.3.1"
lodash.flow "^3.5.0"
markdown-it-ins@^3.0.1:
version "3.0.1"
resolved "https://registry.npmmirror.com/markdown-it-ins/-/markdown-it-ins-3.0.1.tgz#c09356b917cf1dbf73add0b275d67ab8c73d4b4d"
integrity sha512-32SSfZqSzqyAmmQ4SHvhxbFqSzPDqsZgMHDwxqPzp+v+t8RsmqsBZRG+RfRQskJko9PfKC2/oxyOs4Yg/CfiRw==
markdown-it-mark@^3.0.1:
version "3.0.1"
resolved "https://registry.npmmirror.com/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz#51257db58787d78aaf46dc13418d99a9f3f0ebd3"
integrity sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A==
markdown-it-sub@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz#375fd6026eae7ddcb012497f6411195ea1e3afe8"
integrity sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q==
markdown-it-sup@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz#cb9c9ff91a5255ac08f3fd3d63286e15df0a1fc3"
integrity sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ==
markdown-it-task-lists@^2.1.1:
version "2.1.1"
resolved "https://registry.npmmirror.com/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz#f68f4d2ac2bad5a2c373ba93081a1a6848417088"
integrity sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==
markdown-it-toc-done-right@^4.2.0:
version "4.2.0"
resolved "https://registry.npmmirror.com/markdown-it-toc-done-right/-/markdown-it-toc-done-right-4.2.0.tgz#3ccdce22d5022ffae7b991d07261b1b1de5459d0"
integrity sha512-UB/IbzjWazwTlNAX0pvWNlJS8NKsOQ4syrXZQ/C72j+jirrsjVRT627lCaylrKJFBQWfRsPmIVQie8x38DEhAQ==
markdown-it@^12.3.2:
version "12.3.2"
resolved "https://registry.npmmirror.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90"
integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==
dependencies:
argparse "^2.0.1"
entities "~2.1.0"
linkify-it "^3.0.1"
mdurl "^1.0.1"
uc.micro "^1.0.5"
mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.npmmirror.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==
meow@^8.0.0:
version "8.1.2"
resolved "https://registry.npmmirror.com/meow/-/meow-8.1.2.tgz"
@ -4685,6 +4759,11 @@ typescript@^4.6.4, typescript@^4.9.3:
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz"
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.npmmirror.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
ufo@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/ufo/-/ufo-1.0.1.tgz"
@ -4996,6 +5075,25 @@ vue-types@^3.0.0:
dependencies:
is-plain-object "3.0.1"
vue3-markdown-it@^1.0.10:
version "1.0.10"
resolved "https://registry.npmmirror.com/vue3-markdown-it/-/vue3-markdown-it-1.0.10.tgz#c4b0e94990b3f9d66123ef5b775cd7ea177c609b"
integrity sha512-mTvHu0zl7jrh7ojgaZ+tTpCLiS4CVg4bTgTu4KGhw/cRRY5YgIG8QgFAPu6kCzSW6Znc9a52Beb6hFvF4hSMkQ==
dependencies:
markdown-it "^12.3.2"
markdown-it-abbr "^1.0.4"
markdown-it-anchor "^8.4.1"
markdown-it-deflist "^2.1.0"
markdown-it-emoji "^2.0.0"
markdown-it-footnote "^3.0.3"
markdown-it-highlightjs "^3.6.0"
markdown-it-ins "^3.0.1"
markdown-it-mark "^3.0.1"
markdown-it-sub "^1.0.0"
markdown-it-sup "^1.0.0"
markdown-it-task-lists "^2.1.1"
markdown-it-toc-done-right "^4.2.0"
vue@^3.2.45:
version "3.2.45"
resolved "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz"