Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev

This commit is contained in:
leiqiaochu 2023-03-08 09:14:31 +08:00
commit c932dc015f
54 changed files with 2433 additions and 1820 deletions

View File

@ -8,213 +8,214 @@ export interface IMatcher {
const matchComponents: IMatcher[] = [
{
pattern: /^Avatar/,
styleDir: 'Avatar',
styleDir: 'Avatar'
},
{
pattern: /^AutoComplete/,
styleDir: 'AutoComplete',
styleDir: 'AutoComplete'
},
{
pattern: /^Anchor/,
styleDir: 'Anchor',
styleDir: 'Anchor'
},
{
pattern: /^Badge/,
styleDir: 'Badge',
styleDir: 'Badge'
},
{
pattern: /^Breadcrumb/,
styleDir: 'Breadcrumb',
styleDir: 'Breadcrumb'
},
{
pattern: /^Button/,
styleDir: 'Button',
styleDir: 'Button'
},
{
pattern: /^Checkbox/,
styleDir: 'Checkbox',
styleDir: 'Checkbox'
},
{
pattern: /^CardSelect/,
styleDir: 'CardSelect'
},
{
pattern: /^Card/,
styleDir: 'Card',
styleDir: 'Card'
},
{
pattern: /^Collapse/,
styleDir: 'Collapse',
styleDir: 'Collapse'
},
{
pattern: /^Descriptions/,
styleDir: 'Descriptions',
styleDir: 'Descriptions'
},
{
pattern: /^RangePicker|^WeekPicker|^MonthPicker/,
styleDir: 'DatePicker',
styleDir: 'DatePicker'
},
{
pattern: /^Dropdown/,
styleDir: 'Dropdown',
styleDir: 'Dropdown'
},
{
pattern: /^Form/,
styleDir: 'Form',
styleDir: 'Form'
},
{
pattern: /^InputNumber/,
styleDir: 'InputNumber',
styleDir: 'InputNumber'
},
{
pattern: /^Input|^Textarea/,
styleDir: 'Input',
styleDir: 'Input'
},
{
pattern: /^Statistic/,
styleDir: 'Statistic',
styleDir: 'Statistic'
},
{
pattern: /^CheckableTag/,
styleDir: 'Tag',
styleDir: 'Tag'
},
{
pattern: /^TimeRangePicker/,
styleDir: 'TimePicker',
styleDir: 'TimePicker'
},
{
pattern: /^Layout/,
styleDir: 'Layout',
styleDir: 'Layout'
},
{
pattern: /^Menu|^SubMenu/,
styleDir: 'Menu',
styleDir: 'Menu'
},
{
pattern: /^Table/,
styleDir: 'Table',
styleDir: 'Table'
},
{
pattern: /^TimePicker|^TimeRangePicker/,
styleDir: 'TimeTicker',
styleDir: 'TimeTicker'
},
{
pattern: /^Radio/,
styleDir: 'Radio',
styleDir: 'Radio'
},
{
pattern: /^Image/,
styleDir: 'Image',
styleDir: 'Image'
},
{
pattern: /^List/,
styleDir: 'List',
styleDir: 'List'
},
{
pattern: /^Tab/,
styleDir: 'Tabs',
styleDir: 'Tabs'
},
{
pattern: /^Mentions/,
styleDir: 'Mentions',
styleDir: 'Mentions'
},
{
pattern: /^Step/,
styleDir: 'Steps',
styleDir: 'Steps'
},
{
pattern: /^Skeleton/,
styleDir: 'Skeleton',
styleDir: 'Skeleton'
},
{
pattern: /^Select/,
styleDir: 'Select',
styleDir: 'Select'
},
{
pattern: /^TreeSelect/,
styleDir: 'TreeSelect',
styleDir: 'TreeSelect'
},
{
pattern: /^Tree|^DirectoryTree/,
styleDir: 'Tree',
styleDir: 'Tree'
},
{
pattern: /^Typography/,
styleDir: 'Typography',
styleDir: 'Typography'
},
{
pattern: /^Timeline/,
styleDir: 'Timeline',
styleDir: 'Timeline'
},
{
pattern: /^Upload/,
styleDir: 'Upload',
styleDir: 'Upload'
},
{
pattern: /^ProTable/,
styleDir: 'ProTable',
styleDir: 'ProTable'
},
{
pattern: /^Search|^AdvancedSearch/,
styleDir: 'Search',
styleDir: 'Search'
},
{
pattern: /^Ellipsis/,
styleDir: 'Ellipsis',
styleDir: 'Ellipsis'
},
{
pattern: /^MonacoEditor/,
styleDir: 'MonacoEditor',
styleDir: 'MonacoEditor'
},
{
pattern: /^ProLayout/,
styleDir: 'ProLayout',
styleDir: 'ProLayout'
},
{
pattern: /^ScrollTable/,
styleDir: 'ScrollTable',
styleDir: 'ScrollTable'
},
{
pattern: /^TableCard/,
styleDir: 'TableCard',
styleDir: 'TableCard'
},
{
pattern: /^Scrollbar/,
styleDir: 'Scrollbar',
styleDir: 'Scrollbar'
},
{
pattern: /^AIcon/,
styleDir: 'AIcon',
},
{
pattern: /^CardSelect/,
styleDir: 'CardSelect',
styleDir: 'AIcon'
},
{
pattern: /^Tooltip/,
styleDir: 'Tooltip',
styleDir: 'Tooltip'
},
{
pattern: /^Empty/,
styleDir: 'Empty',
styleDir: 'Empty'
},
{
pattern: /^Popconfirm/,
styleDir: 'Popconfirm',
styleDir: 'Popconfirm'
},
{
pattern: /^message/,
styleDir: 'Message',
styleDir: 'Message'
},
{
pattern: /^Notification/,
styleDir: 'Notification',
},
styleDir: 'Notification'
}
]
export interface JetlinksVueResolverOptions {
@ -259,7 +260,6 @@ function getStyleDir(compName: string, _isAntd = false): string {
let styleDir
const components = _isAntd ? AntdMatchComponents : matchComponents
const total = components.length
console.log('getStyleDir', compName)
for (let i = 0; i < total; i++) {
const matcher = components[i]
if (compName.match(matcher.pattern)) {
@ -273,10 +273,10 @@ function getStyleDir(compName: string, _isAntd = false): string {
return styleDir
}
function getSideEffects(compName: string, options: JetlinksVueResolverOptions, _isAntd= false): any {
function getSideEffects(compName: string, options: JetlinksVueResolverOptions, _isAntd = false): any {
const {
importStyle = true,
importLess = false,
importLess = false
} = options
if (!importStyle)
@ -286,23 +286,21 @@ function getSideEffects(compName: string, options: JetlinksVueResolverOptions, _
if (importStyle === 'less' || importLess) {
const styleDir = getStyleDir(compName, _isAntd)
console.log('getSideEffects-style-path', `${packageName}/${lib}/${styleDir}/style`)
return `${packageName}/${lib}/${styleDir}/style`
}
else {
} else {
const styleDir = getStyleDir(compName, _isAntd)
return `${packageName}/${lib}/${styleDir}/style/css`
}
}
const filterName = [ 'message', 'Notification', 'AIcon']
const filterName = ['message', 'Notification', 'AIcon']
const primitiveNames = ['Affix', 'Anchor', 'AnchorLink', 'message', 'Notification', 'AutoComplete', 'AutoCompleteOptGroup', 'AutoCompleteOption', 'Alert', 'Avatar', 'AvatarGroup', 'BackTop', 'Badge', 'BadgeRibbon', 'Breadcrumb', 'BreadcrumbItem', 'BreadcrumbSeparator', 'Button', 'ButtonGroup', 'Calendar', 'Card', 'CardGrid', 'CardMeta', 'Collapse', 'CollapsePanel', 'Carousel', 'Cascader', 'Checkbox', 'CheckboxGroup', 'Col', 'Comment', 'ConfigProvider', 'DatePicker', 'MonthPicker', 'WeekPicker', 'RangePicker', 'QuarterPicker', 'Descriptions', 'DescriptionsItem', 'Divider', 'Dropdown', 'DropdownButton', 'Drawer', 'Empty', 'Form', 'FormItem', 'FormItemRest', 'Grid', 'Input', 'InputGroup', 'InputPassword', 'InputSearch', 'Textarea', 'Image', 'ImagePreviewGroup', 'InputNumber', 'Layout', 'LayoutHeader', 'LayoutSider', 'LayoutFooter', 'LayoutContent', 'List', 'ListItem', 'ListItemMeta', 'Menu', 'MenuDivider', 'MenuItem', 'MenuItemGroup', 'SubMenu', 'Mentions', 'MentionsOption', 'Modal', 'Statistic', 'StatisticCountdown', 'PageHeader', 'Pagination', 'Popconfirm', 'Popover', 'Progress', 'Radio', 'RadioButton', 'RadioGroup', 'Rate', 'Result', 'Row', 'Select', 'SelectOptGroup', 'SelectOption', 'Skeleton', 'SkeletonButton', 'SkeletonAvatar', 'SkeletonInput', 'SkeletonImage', 'Slider', 'Space', 'Spin', 'Steps', 'Step', 'Switch', 'Table', 'TableColumn', 'TableColumnGroup', 'TableSummary', 'TableSummaryRow', 'TableSummaryCell', 'Transfer', 'Tree', 'TreeNode', 'DirectoryTree', 'TreeSelect', 'TreeSelectNode', 'Tabs', 'TabPane', 'Tag', 'CheckableTag', 'TimePicker', 'TimeRangePicker', 'Timeline', 'TimelineItem', 'Tooltip', 'Typography', 'TypographyLink', 'TypographyParagraph', 'TypographyText', 'TypographyTitle', 'Upload', 'UploadDragger', 'LocaleProvider', 'ProTable', 'Search', 'AdvancedSearch', 'Ellipsis', 'MonacoEditor', 'ProLayout', 'ScrollTable', 'TableCard', 'Scrollbar', 'CardSelect', 'ColorPicker']
const prefix = 'J'
let jetlinksNames: Set<string>
function genJetlinksNames(primitiveNames: string[]): void {
jetlinksNames = new Set(primitiveNames.map(name => filterName.includes(name) ? name : `${prefix}${name}`))
jetlinksNames = new Set(primitiveNames.map(name => filterName.includes(name) ? name : `${prefix}${name}`))
}
let antdvNames: Set<string>
@ -322,16 +320,14 @@ function isAntdv(compName: string): boolean {
return antdvNames.has(compName)
}
export function JetlinksVueResolver(options: JetlinksVueResolverOptions = {
}): any {
export function JetlinksVueResolver(options: JetlinksVueResolverOptions = {}): any {
return {
type: 'component',
resolve: (name: string) => {
if (options.resolveIcons && name.match(/(Outlined|Filled|TwoTone)$/)) {
return {
name,
from: '@ant-design/icons-vue',
from: '@ant-design/icons-vue'
}
}
const _isJetlinks = isJetlinks(name)
@ -340,13 +336,16 @@ export function JetlinksVueResolver(options: JetlinksVueResolverOptions = {
const importName = filterName.includes(name) ? name : name.slice(1)
options.packageName = _isJetlinks ? 'jetlinks-ui-components' : 'ant-design-vue'
const path = `${options.packageName}/${options.cjs ? 'lib' : 'es'}`
const stylePath = getSideEffects(importName, options, _isAntd)
if (_isJetlinks) {
console.log(name, importName, stylePath)
}
return {
name: importName,
from: path,
sideEffects: getSideEffects(importName, options, _isAntd),
sideEffects: stylePath
}
}
},
}
}
}

View File

@ -21,4 +21,6 @@ export const _action = (id: string, type: '_disable' | '_enable') => server.put(
export const _execute = (id: string) => server.post(`/scene/${id}/_execute`);
// 内置参数
export const queryBuiltInParams = (data: any, params?: any) => server.post(`/scene/parse-variables`, data, params);
export const queryBuiltInParams = (data: any, params?: any) => server.post(`/scene/parse-variables`, data, params);
export const getParseTerm = (data: Record<string, any>) => server.post(`/scene/parse-term-column`, data)

View File

@ -13,12 +13,12 @@
}}</a>
<span v-else>{{ slotProps.route.breadcrumbName }}</span>
</template>
<template #rightContentRender>
<div style="margin-right: 24px;display: flex;align-items: center;">
<AIcon type="QuestionCircleOutlined" @click="toDoc" />
<Notice style="margin: 0 24px;" />
<UserInfo />
</div>
<template #rightContentRender>
<div class="right-content">
<AIcon type="QuestionCircleOutlined" @click="toDoc" />
<Notice style="margin: 0 24px" />
<UserInfo />
</div>
</template>
<router-view v-slot="{ Component }">
<component :is="Component" />
@ -28,7 +28,7 @@
<script setup lang="ts" name="BasicLayoutPage">
import UserInfo from './components/UserInfo.vue';
import Notice from './components/Notice.vue'
import Notice from './components/Notice.vue';
import DefaultSetting from '../../../config/config';
import { useMenuStore } from '@/store/menu';
import { clearMenuItem } from 'jetlinks-ui-components/es/ProLayout/util';
@ -95,8 +95,13 @@ watchEffect(() => {
}
});
const toDoc = ()=>window.open('http://doc.v2.jetlinks.cn/')
const toDoc = () => window.open('http://doc.v2.jetlinks.cn/');
</script>
<style scoped></style>
<style scoped>
.right-content {
margin-right: 24px;
display: flex;
align-items: center;
}
</style>

View File

@ -10,7 +10,7 @@ import NormalUpload from './NormalUpload/index.vue'
import FileFormat from './FileFormat/index.vue'
import JProUpload from './JUpload/index.vue'
import { BasicLayoutPage, BlankLayoutPage } from './Layout'
import { PageContainer } from 'jetlinks-ui-components/es/components'
import { PageContainer } from 'jetlinks-ui-components'
import Ellipsis from './Ellipsis/index.vue'
import JEmpty from './Empty/index.vue'
import AMapComponent from './AMapComponent/index.vue'

View File

@ -76,7 +76,7 @@ export const useMenuStore = defineStore({
const path = this.hasMenu(name)
if (path) {
router.push({
name, params, query
name, params, query, state: { params }
})
} else {
onlyMessage('暂无权限,请联系管理员', 'error')

View File

@ -92,7 +92,8 @@ export type MenuItem = {
icon?: string
[key: string]: any
},
component?: any
component?: any,
props?: boolean
};
// 额外子级路由
@ -213,7 +214,7 @@ export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level =
title: route.name,
hideInMenu: route.isShow === false,
buttons: route.buttons?.map((b: any) => b.id) || []
}
},
}
const silder = {..._route}

View File

@ -1,13 +1,12 @@
<template>
<a-card class="boot-card-container" :bordered="false">
<template #title>
<h5 class="title">{{ cardTitle }}</h5>
</template>
<div class="boot-card-container" :bordered="false">
<h5 class="title">{{ cardTitle }}</h5>
<div class="box">
<div
class="box-item"
v-for="(item, index) in cardData"
@click="jumpPage(item)"
@click="jumpPage(item.link,item.params)"
>
<div class="item-english">{{ item.english }}</div>
<div class="item-title">{{ item.label }}</div>
@ -18,46 +17,27 @@
/>
</div>
</div>
</a-card>
</div>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue';
import { bootConfig } from "../index";
import { bootConfig } from "../typing";
import { useMenuStore } from '@/store/menu';
const { jumpPage } = useMenuStore();
const router = useRouter();
const props = defineProps({
cardData: Array<bootConfig>,
cardTitle: String,
});
const { cardData, cardTitle } = toRefs(props);
const jumpPage = (row: bootConfig): void => {
if (row.auth && row.link) {
router.push(`${row.link}${objToParams(row.params || {})}`);
} else {
message.warning('暂无权限,请联系管理员');
}
};
const objToParams = (source: object): string => {
if (Object.prototype.toString.call(source) === '[object Object]') {
const paramsArr = <any>[];
Object.entries(source).forEach(([prop, value]) => {
if (typeof value === 'object') value = JSON.stringify(value);
paramsArr.push(`${prop}=${value}`);
});
if (paramsArr.length > 0) return '?' + paramsArr.join('&');
}
return '';
};
</script>
<style lang="less" scoped>
.boot-card-container {
:deep(.ant-card-body) {
padding-top: 0;
}
background-color: #fff;
padding: 24px 14px;
.title {
position: relative;
z-index: 2;

View File

@ -28,7 +28,7 @@
<script setup lang="ts">
import { message } from 'ant-design-vue';
import { bootConfig } from "../index";
import { bootConfig } from "../typing";
const router = useRouter();
const props = defineProps({

View File

@ -43,7 +43,7 @@ import StepCard from '../StepCard.vue';
import { useMenuStore } from '@/store/menu';
import { usePermissionStore } from '@/store/permission';
import { recommendList, bootConfig } from '../../index';
import { recommendList, bootConfig } from '../../typing';
//
const hasPermission = usePermissionStore().hasPermission;

View File

@ -28,7 +28,7 @@ import PlatformPicCard from '../PlatformPicCard.vue';
import StepCard from '../StepCard.vue';
import { useMenuStore } from "@/store/menu";
import { bootConfig, recommendList } from '../../index';
import { bootConfig, recommendList } from '../../typing';
//
const menuPermission = useMenuStore().hasPermission;

View File

@ -1,13 +1,7 @@
<template>
<a-card class="device-count-container">
<template #title>
<h5 class="title">设备统计</h5>
</template>
<template #extra>
<span style="color: #1d39c4; cursor: pointer" @click="jumpPage"
>详情</span
>
</template>
<div class="device-count-container">
<h5 class="title">设备统计</h5>
<span class="detail" @click="jumpPage('device/DashBoard')"> 详情 </span>
<div class="box-list">
<div class="box-item">
@ -21,34 +15,33 @@
<img src="/images/home/top-2.png" alt="" />
</div>
</div>
</a-card>
</div>
</template>
<script setup lang="ts">
import { getDeviceCount_api, getProductCount_api } from '@/api/home';
import { useMenuStore } from '@/store/menu';
const { jumpPage } = useMenuStore();
const projectNum = ref(0);
const deviceNum = ref(0);
onMounted(() => {
getData();
});
const getData = () => {
getDeviceCount_api().then((resp) => {
getDeviceCount_api().then((resp: any) => {
deviceNum.value = resp.result;
});
getProductCount_api().then((resp) => {
getProductCount_api({}).then((resp: any) => {
projectNum.value = resp.result;
});
};
const jumpPage = () => {};
getData();
</script>
<style lang="less" scoped>
.device-count-container {
:deep(.ant-card-body) {
padding-top: 0;
}
background-color: #fff;
padding: 24px 14px;
position: relative;
.title {
position: relative;
z-index: 2;
@ -71,6 +64,14 @@ const jumpPage = () => {};
content: ' ';
}
}
.detail {
color: #1d39c4;
cursor: pointer;
position: absolute;
right: 12px;
top: 24px;
z-index: 3;
}
.box-list {
display: grid;

View File

@ -1,6 +1,6 @@
<template>
<div class="device-home-container">
<a-row :gutter="10">
<a-row :gutter="24">
<a-col :span="14">
<BootCard :cardData="deviceBootConfig" cardTitle="物联网引导" />
</a-col>
@ -18,7 +18,7 @@
:dataList="deviceStepDetails"
/>
</a-row>
</div>
</div>
</template>
<script setup lang="ts" name="deviceHome">
@ -28,7 +28,7 @@ import PlatformPicCard from '../PlatformPicCard.vue';
import StepCard from '../StepCard.vue';
import { usePermissionStore } from '@/store/permission';
import { bootConfig, recommendList } from '../../index';
import { bootConfig, recommendList } from '../../typing';
//
const hasPermission = usePermissionStore().hasPermission;
@ -43,28 +43,28 @@ const deviceBootConfig: bootConfig[] = [
{
english: 'STEP1',
label: '创建产品',
link: '/iot/device/Product',
link: 'device/Product',
auth: productPermission('add'),
params: {
save: true,
type: 'add',
},
},
{
english: 'STEP2',
label: '创建设备',
link: '/iot/device/Instance',
link: 'device/Instance',
auth: devicePermission('add'),
params: {
save: true,
type: 'add',
},
},
{
english: 'STEP3',
label: '规则引擎',
link: '/iot/rule-engine/Instance',
link: 'rule-engine/Instance',
auth: rulePermission('add'),
params: {
save: true,
type: 'add',
},
},
];
@ -74,10 +74,10 @@ const deviceStepDetails: recommendList[] = [
details:
'产品是设备的集合,通常指一组具有相同功能的设备。物联设备必须通过产品进行接入方式配置。',
iconUrl: '/images/home/bottom-4.png',
linkUrl: '/iot/device/Product',
linkUrl: 'device/Product',
auth: productPermission('add'),
params: {
save: true,
type: 'add',
},
},
{
@ -85,7 +85,7 @@ const deviceStepDetails: recommendList[] = [
details:
'通过产品对同一类型的设备进行统一的接入方式配置。请参照设备铭牌说明选择匹配的接入方式。',
iconUrl: '/images/home/bottom-1.png',
linkUrl: '/iot/device/Product/detail',
linkUrl: 'device/Product/Detail',
auth: productPermission('update'),
dialogTag: 'accessMethod',
},
@ -93,10 +93,10 @@ const deviceStepDetails: recommendList[] = [
title: '添加测试设备',
details: '添加单个设备,用于验证产品模型是否配置正确。',
iconUrl: '/images/home/bottom-5.png',
linkUrl: '/iot/device/Instance',
linkUrl: 'device/Instance',
auth: devicePermission('add'),
params: {
save: true,
type: 'add',
},
},
{
@ -104,8 +104,7 @@ const deviceStepDetails: recommendList[] = [
details:
'对添加的测试设备进行功能调试,验证能否连接到平台,设备功能是否配置正确。',
iconUrl: '/images/home/bottom-2.png',
linkUrl: '/iot/device/Instance/detail',
// auth: devicePermission('update'),
linkUrl: 'device/Instance/Detail',
auth: true,
dialogTag: 'funcTest',
},
@ -113,7 +112,7 @@ const deviceStepDetails: recommendList[] = [
title: '批量添加设备',
details: '批量添加同一产品下的设备',
iconUrl: '/images/home/bottom-3.png',
linkUrl: '/iot/device/Instance',
linkUrl: 'device/Instance',
auth: devicePermission('import'),
params: {
import: true,

View File

@ -1,5 +1,5 @@
<template>
<a-card class="platform-pic-container">
<div class="platform-pic-container">
<div class="title">
<span>平台架构图</span>
<p>PLATFORM ARCHITECTURE DIAGRAM</p>
@ -9,7 +9,7 @@
class="bj"
alt=""
/>
</a-card>
</div>
</template>
<script setup lang="ts">
@ -19,13 +19,11 @@ const props = defineProps({
</script>
<style lang="less" scoped>
:deep(.ant-card-body) {
padding: 0;
}
.platform-pic-container {
position: relative;
width: 100%;
overflow: hidden;
background-color: #fff;
border-bottom: 1px solid #2f54eb;
.bj {

View File

@ -1,17 +1,17 @@
<template>
<a-card class="step-container">
<dic class="step-container">
<h5 class="title">
<span style="margin-right: 12px">{{ cardTitle }}</span>
<a-tooltip placement="top">
<span style="margin-right: 12px">{{ props.cardTitle }}</span>
<j-tooltip placement="top">
<template #title>
<span>{{ tooltip }}</span>
<span>{{ props.tooltip }}</span>
</template>
<question-circle-outlined style="padding-top: 5px" />
</a-tooltip>
<AIcon type="QuestionCircleOutlined" style="padding-top: 5px" />
</j-tooltip>
</h5>
<div class="box-list">
<div class="list-item" v-for="item in dataList">
<div class="list-item" v-for="item in props.dataList">
<div class="box-top" @click="jumpPage(item)">
<span class="top-title">{{ item.title }}</span>
<img :src="item.iconUrl" alt="" />
@ -22,42 +22,35 @@
<div class="dialogs">
<ProductChooseDialog
:open-number="openAccess"
v-if="productDialogVisible"
v-model:visible="productDialogVisible"
@confirm="againJumpPage"
/>
<DeviceChooseDialog
:open-number="openFunc"
v-if="deviceDialogVisible"
v-model:visible="deviceDialogVisible"
@confirm="againJumpPage"
/>
</div>
</a-card>
</dic>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import ProductChooseDialog from './dialogs/ProductChooseDialog.vue';
import DeviceChooseDialog from './dialogs/DeviceChooseDialog.vue';
import { recommendList } from '../typing';
import { useMenuStore } from '@/store/menu';
import { recommendList } from '../index';
type rowType = {
params: object;
linkUrl: string;
};
const { jumpPage: _jumpPage } = useMenuStore();
const props = defineProps({
cardTitle: String,
tooltip: String,
dataList: Array as PropType<recommendList[]>,
});
const router = useRouter();
const { cardTitle, tooltip, dataList } = toRefs(props);
const openAccess = ref<number>(0);
const openFunc = ref<number>(0);
let selectRow: recommendList | rowType = {
params: {},
linkUrl: '',
@ -66,34 +59,33 @@ let selectRow: recommendList | rowType = {
const jumpPage = (row: recommendList) => {
if (!row.auth) return message.warning('暂无权限,请联系管理员');
selectRow = row; // 使
if (row.dialogTag == 'accessMethod') return (openAccess.value += 1);
else if (row.dialogTag === 'funcTest') return (openFunc.value += 1);
if (row.dialogTag == 'accessMethod')
return (productDialogVisible.value = true);
else if (row.dialogTag === 'funcTest')
return (deviceDialogVisible.value = true);
else if (row.linkUrl) {
router.push(`${row.linkUrl}${objToParams(row.params || {})}`);
_jumpPage(row.linkUrl, row.params);
}
};
//
const againJumpPage = (params: string) => {
router.push(`${selectRow.linkUrl}/${params}`);
const againJumpPage = (id: string) => {
_jumpPage(selectRow.linkUrl, { id });
};
const objToParams = (source: object): string => {
if (Object.prototype.toString.call(source) === '[object Object]') {
const paramsArr = <any>[];
// 使for ints
Object.entries(source).forEach(([prop, value]) => {
if (typeof value === 'object') value = JSON.stringify(value);
paramsArr.push(`${prop}=${value}`);
});
if (paramsArr.length > 0) return '?' + paramsArr.join('&');
}
return '';
const productDialogVisible = ref(false);
const deviceDialogVisible = ref(false);
type rowType = {
params: object;
linkUrl: string;
};
</script>
<style lang="less" scoped>
.step-container {
width: 100%;
padding: 24px 14px;
background-color: #fff;
.title {
position: relative;
z-index: 2;

View File

@ -1,23 +1,26 @@
<template>
<div ref="modal" class="func-test-dialog-container"></div>
<a-modal
v-model:visible="visible"
title="选择产品"
<j-modal
visible
title="选择设备"
style="width: 1000px"
@ok="handleOk"
:getContainer="getContainer"
@ok="confirm"
@cancel="emits('update:visible', false)"
:maskClosable="false"
>
<Search type="simple" :columns="query.columns" @search="query.search" />
<JTable
<j-advanced-search
type="simple"
:columns="columns"
@search="(params:any)=>queryParams = {...params}"
/>
<j-pro-table
model="TABLE"
:request="getDeviceList_api"
:columns="table.columns"
:params="query.params"
:columns="columns"
:params="queryParams"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
:rowSelection="{
selectedRowKeys: table.selectedKeys,
onChange: table.onSelect,
selectedRowKeys: selectedKeys,
onChange: (keys:string[])=>selectedKeys = keys,
type: 'radio',
}"
>
@ -32,152 +35,90 @@
:status-label="slotProps.state.text"
/>
</template>
</JTable>
<template #footer>
<a-button key="back" @click="visible = false">取消</a-button>
<a-button key="submit" type="primary" @click="handleOk"
>确认</a-button
>
</template>
</a-modal>
</j-pro-table>
</j-modal>
</template>
<script setup lang="ts">
import StatusLabel from '../StatusLabel.vue';
import { ComponentInternalInstance } from 'vue';
import { getDeviceList_api } from '@/api/home';
import { message } from 'ant-design-vue';
import moment from 'moment';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const emits = defineEmits(['confirm']);
const props = defineProps({
openNumber: Number,
});
const emits = defineEmits(['confirm', 'update:visible']);
const props = defineProps<{
visible: boolean;
}>();
//
const visible = ref<boolean>(false);
const getContainer = () => proxy?.$refs.modal as HTMLElement;
const handleOk = () => {
if (table.selectedKeys.length < 1) return message.warn('请选择设备');
emits('confirm', table.selectedKeys[0]);
visible.value = false;
const confirm = () => {
if (selectedKeys.value.length < 1) return message.warn('请选择设备');
emits('confirm', selectedKeys.value[0]);
emits('update:visible', false);
};
watch(
() => props.openNumber,
() => {
visible.value = true;
},
);
const query = reactive({
params: {},
columns: [
{
title: '设备ID',
dataIndex: 'id',
key: 'id',
ellipsis: true,
search: {
type: 'string',
},
const columns = [
{
title: '设备ID',
dataIndex: 'id',
key: 'id',
ellipsis: true,
search: {
type: 'string',
},
{
title: '设备名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '产品名称',
dataIndex: 'productName',
key: 'productName',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '注册时间',
dataIndex: 'modifyTime',
key: 'modifyTime',
ellipsis: true,
search: {
type: 'date',
},
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
ellipsis: true,
search: {
rename: 'state',
type: 'select',
options: [
{
label: '在线',
value: 'online',
},
{
label: '离线',
value: 'offline',
},
],
},
},
],
search: (params: object) => {
query.params = params;
},
});
{
title: '设备名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '产品名称',
dataIndex: 'productName',
key: 'productName',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '注册时间',
dataIndex: 'modifyTime',
key: 'modifyTime',
ellipsis: true,
search: {
type: 'date',
},
scopedSlots: true,
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
ellipsis: true,
search: {
rename: 'state',
type: 'select',
options: [
{
label: '在线',
value: 'online',
},
{
label: '离线',
value: 'offline',
},
],
},
scopedSlots: true,
},
];
const table = reactive({
columns: [
{
title: '设备Id',
dataIndex: 'id',
key: 'id',
},
{
title: '设备名称',
dataIndex: 'name',
key: 'name',
},
{
title: '产品名称',
dataIndex: 'productName',
key: 'productName',
},
{
title: '注册时间',
dataIndex: 'modifyTime',
key: 'modifyTime',
scopedSlots: true,
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
scopedSlots: true,
},
],
selectedKeys: [] as string[],
onSelect: (keys: string[]) => {
table.selectedKeys = [...keys];
},
});
const queryParams = ref({});
const selectedKeys = ref<string[]>([]);
</script>
<style lang="less" scoped>
.func-test-dialog-container {
.search {
display: flex;
}
}
</style>

View File

@ -1,59 +1,52 @@
<template>
<div ref="modal" class="access-method-dialog-container"></div>
<a-modal
v-model:visible="visible"
<j-modal
visible
title="选择产品"
style="width: 700px"
@ok="handleOk"
show-search
width="700px"
@ok="confirm"
@cancel="emits('update:visible', false)"
:filter-option="filterOption"
:getContainer="getContainer"
:maskClosable="false"
class="access-method-dialog-container"
>
<a-form :model="form" name="basic" autocomplete="off" layout="vertical">
<a-form-item
<j-form :model="form" name="basic" autocomplete="off" layout="vertical">
<j-form-item
label="产品"
name="productId"
:rules="[{ required: true, message: '该字段是必填字段' }]"
>
<a-select
<j-select
v-model:value="form.productId"
style="width: 100%"
:options="productList"
>
</a-select>
</a-form-item>
</a-form>
<template #footer>
<a-button key="back" @click="visible = false">取消</a-button>
<a-button key="submit" type="primary" @click="handleOk"
>确认</a-button
>
</template>
</a-modal>
</j-select>
</j-form-item>
</j-form>
</j-modal>
</template>
<script setup lang="ts">
import { ComponentInternalInstance } from 'vue';
import { getProductList_api } from '@/api/home';
import { productItem } from '../../index';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
openNumber: Number,
});
const emits = defineEmits(['confirm']);
const visible = ref<boolean>(false);
import { productItem } from '../../typing';
const emits = defineEmits(['confirm', 'update:visible']);
const props = defineProps<{
visible: boolean;
}>();
const confirm = () => {
emits('confirm', form.value.productId);
emits('update:visible', false);
};
const form = ref({
productId: '',
});
const productList = ref<[productItem] | []>([]);
const getContainer = () => proxy?.$refs.modal as HTMLElement;
const getOptions = () => {
getProductList_api().then((resp:any) => {
getProductList_api().then((resp: any) => {
productList.value = resp.result
.filter((i: any) => !i?.accessId)
.map((item: { name: any; id: any }) => ({
@ -62,21 +55,11 @@ const getOptions = () => {
})) as [productItem];
});
};
const handleOk = () => {
emits('confirm', form.value.productId);
visible.value = false;
};
const filterOption = (input: string, option: any) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
watch(
() => props.openNumber,
() => {
visible.value = true;
form.value.productId = '';
getOptions();
},
);
getOptions();
</script>
<style lang="less" scoped>

View File

@ -17,7 +17,13 @@
:gridColumn="2"
>
<template #headerTitle>
<a-button type="primary" @click="handleAdd"> 新增 </a-button>
<PermissionButton
type="primary"
@click="handleAdd"
hasPermission="media/Cascade:add"
>
<template #icon><AIcon type="PlusOutlined" />新增</template>
</PermissionButton>
</template>
<template #card="slotProps">
<CardBox
@ -58,48 +64,14 @@
</Ellipsis>
</template>
<template #actions="item">
<a-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
<a-popconfirm
v-if="item.popConfirm"
v-bind="item.popConfirm"
:disabled="item.disabled"
>
<a-button
:disabled="item.disabled"
v-if="item.key === 'delete'"
>
<AIcon type="DeleteOutlined" />
</a-button>
<a-button
:disabled="item.disabled"
@click="item.onClick"
v-else
>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</a-button>
</a-popconfirm>
<template v-else>
<a-button
:disabled="item.disabled"
@click="item.onClick"
>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</a-button>
</template>
</a-tooltip>
<!-- <PermissionButton
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="`media/Cascade:${item.key}`"
:hasPermission="'media/Cascade:' + item.key"
>
<AIcon
type="DeleteOutlined"
@ -109,7 +81,7 @@
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton> -->
</PermissionButton>
</template>
</CardBox>
</template>
@ -141,38 +113,7 @@
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
<!-- <template
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
@ -185,11 +126,11 @@
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="`device/Instance:${i.key}`"
:hasPermission="'media/Cascade:' + i.key"
>
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template> -->
</template>
</a-space>
</template>
</JProTable>
@ -331,7 +272,7 @@ const getActions = (
if (!data) return [];
const actions = [
{
key: 'edit',
key: 'update',
text: '编辑',
tooltip: {
title: '编辑',
@ -348,7 +289,7 @@ const getActions = (
},
},
{
key: 'view',
key: 'channel',
text: '选择通道',
tooltip: {
title: '选择通道',
@ -365,7 +306,7 @@ const getActions = (
},
},
{
key: 'debug',
key: 'push',
text: '推送',
tooltip: {
title:

View File

@ -214,6 +214,10 @@ const channelId = computed(() => route.query.channelId as string);
const deviceType = ref('');
/**
* 查询本地视频
* @param date
*/
const queryLocalRecords = async (date: Dayjs) => {
playStatus.value = 0;
url.value = '';
@ -388,17 +392,11 @@ const handlePanelChange = (date: any) => {
*/
const handlePlay = (_startTime: any) => {
if (playStatus.value === 0 || _startTime !== playNowTime.value) {
if (playTimeNode.value) {
playTimeNode.value.playByStartTime(_startTime);
}
playTimeNode.value?.playByStartTime(_startTime);
} else if (playStatus.value == 1 && _startTime === playNowTime.value) {
if (player.value) {
player.value.pause();
}
player.value?.pause();
} else if (playStatus.value == 2 && _startTime === playNowTime.value) {
if (player.value) {
player.value.play();
}
player.value?.play();
}
};
</script>

View File

@ -15,7 +15,13 @@
:params="params"
>
<template #headerTitle>
<a-button type="primary" @click="handleAdd"> 新增 </a-button>
<PermissionButton
type="primary"
@click="handleAdd"
hasPermission="media/Device:add"
>
<template #icon><AIcon type="PlusOutlined" />新增</template>
</PermissionButton>
</template>
<template #card="slotProps">
<CardBox
@ -64,65 +70,47 @@
</a-row>
</template>
<template #actions="item">
<a-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'media/Device:' + item.key"
>
<a-popconfirm
v-if="item.popConfirm"
v-bind="item.popConfirm"
:disabled="item.disabled"
>
<a-button :disabled="item.disabled">
<AIcon type="DeleteOutlined" />
</a-button>
</a-popconfirm>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<a-button
:disabled="item.disabled"
@click="item.onClick"
>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</a-button>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-tooltip>
</PermissionButton>
</template>
</CardBox>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
<PermissionButton
:disabled="i.disabled"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
style="padding: 0"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
style="padding: 0px"
:hasPermission="'media/Device:' + i.key"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</template>
</JProTable>
@ -254,7 +242,7 @@ const getActions = (
if (!data) return [];
const actions = [
{
key: 'edit',
key: 'update',
text: '编辑',
tooltip: {
title: '编辑',
@ -278,9 +266,6 @@ const getActions = (
},
icon: 'PartitionOutlined',
onClick: () => {
// router.push(
// `/media/device/Channel?id=${data.id}&type=${data.provider}`,
// );
menuStory.jumpPage(
'media/Device/Channel',
{},
@ -292,7 +277,7 @@ const getActions = (
},
},
{
key: 'debug',
key: 'view', // updateChannel
text: '更新通道',
tooltip: {
title:
@ -311,6 +296,7 @@ const getActions = (
icon: 'SyncOutlined',
onClick: () => {
// updateChannel()
console.log('updateChannel: ', data);
},
},
{

View File

@ -31,7 +31,7 @@ import StepCard from '@/views/home/components/StepCard.vue';
import BasicCountCard from '@/views/media/Home/components/BasicCountCard.vue';
import { usePermissionStore } from '@/store/permission';
import type { bootConfig, recommendList } from '@/views/home/index';
import type { bootConfig, recommendList } from '@/views/home/typing';
//
const hasPermission = usePermissionStore().hasPermission;

View File

@ -408,6 +408,7 @@ const { resetFields, validate, validateInfos, clearValidate } = useForm(
);
const getDetail = async () => {
console.log('getDetail', route)
if (route.params.id === ':id') return;
const res = await configApi.detail(route.params.id as string);
// formData.value = res.result;

View File

@ -17,16 +17,22 @@
>
<template #headerTitle>
<a-space>
<a-button type="primary" @click="handleAdd">
<PermissionButton
type="primary"
@click="handleAdd"
hasPermission="notice/Config:add"
>
新增
</a-button>
</PermissionButton>
<a-upload
name="file"
accept="json"
:showUploadList="false"
:before-upload="beforeUpload"
>
<a-button>导入</a-button>
<PermissionButton hasPermission="notice/Config:import">
导入
</PermissionButton>
</a-upload>
<a-popconfirm
title="确认导出?"
@ -34,7 +40,9 @@
cancel-text="取消"
@confirm="handleExport"
>
<a-button>导出</a-button>
<PermissionButton hasPermission="notice/Config:export">
导出
</PermissionButton>
</a-popconfirm>
</a-space>
</template>
@ -95,34 +103,45 @@
v-for="(o, i) in item.children"
:key="i"
>
<a-button
<PermissionButton
type="link"
@click="o.onClick"
:hasPermission="`notice/Config:${o.key}`"
>
<AIcon :type="o.icon" />
<template #icon>
<AIcon :type="o.icon" />
</template>
<span>{{ o.text }}</span>
</a-button>
</PermissionButton>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<a-popconfirm
<j-popconfirm
v-else-if="item.key === 'delete'"
v-bind="item.popConfirm"
:disabled="item.disabled"
>
<a-button :disabled="item.disabled">
<AIcon type="DeleteOutlined" />
</a-button>
</a-popconfirm>
<PermissionButton
:disabled="item.disabled"
:hasPermission="`notice/Config:${item.key}`"
>
<template #icon>
<AIcon type="DeleteOutlined" />
</template>
</PermissionButton>
</j-popconfirm>
<template v-else>
<a-button
<PermissionButton
:disabled="item.disabled"
@click="item.onClick"
:hasPermission="`notice/Config:${item.key}`"
>
<AIcon :type="item.icon" />
<template #icon>
<AIcon :type="item.icon" />
</template>
<span>{{ item.text }}</span>
</a-button>
</PermissionButton>
</template>
</a-tooltip>
</template>
@ -130,37 +149,24 @@
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
<PermissionButton
:disabled="i.disabled"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
style="padding: 0"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
style="padding: 0px"
:hasPermission="'notice/Config:' + i.key"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</template>
</JProTable>
@ -324,7 +330,7 @@ const getActions = (
if (!data) return [];
const actions = [
{
key: 'edit',
key: 'update',
text: '编辑',
tooltip: {
title: '编辑',
@ -349,7 +355,7 @@ const getActions = (
},
},
{
key: 'debug',
key: 'log',
text: '通知记录',
tooltip: {
title: '通知记录',
@ -385,7 +391,7 @@ const getActions = (
icon: 'EllipsisOutlined',
children: [
{
key: 'debug',
key: 'export',
text: '导出',
tooltip: {
title: '导出',
@ -396,7 +402,7 @@ const getActions = (
},
},
{
key: 'sync',
key: 'bind',
text: '同步用户',
tooltip: {
title: '同步用户',

View File

@ -17,16 +17,24 @@
>
<template #headerTitle>
<a-space>
<a-button type="primary" @click="handleAdd">
<PermissionButton
type="primary"
@click="handleAdd"
hasPermission="notice/Template:add"
>
新增
</a-button>
</PermissionButton>
<a-upload
name="file"
accept="json"
:showUploadList="false"
:before-upload="beforeUpload"
>
<a-button>导入</a-button>
<PermissionButton
hasPermission="notice/Template:import"
>
导入
</PermissionButton>
</a-upload>
<a-popconfirm
title="确认导出?"
@ -34,7 +42,11 @@
cancel-text="取消"
@confirm="handleExport"
>
<a-button>导出</a-button>
<PermissionButton
hasPermission="notice/Template:export"
>
导出
</PermissionButton>
</a-popconfirm>
</a-space>
</template>
@ -77,42 +89,24 @@
</a-row>
</template>
<template #actions="item">
<a-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'notice/Template:' + item.key"
>
<a-popconfirm
v-if="item.popConfirm"
v-bind="item.popConfirm"
:disabled="item.disabled"
>
<a-button :disabled="item.disabled">
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</template>
</a-button>
</a-popconfirm>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<a-button
:disabled="item.disabled"
@click="item.onClick"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</template>
</a-button>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</a-tooltip>
</PermissionButton>
</template>
</CardBox>
</template>
@ -126,37 +120,24 @@
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
<PermissionButton
:disabled="i.disabled"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
style="padding: 0"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
style="padding: 0px"
:hasPermission="'notice/Template:' + i.key"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</template>
</JProTable>
@ -286,7 +267,7 @@ const handleAdd = () => {
* 导入
*/
const beforeUpload = (file: any) => {
console.log('file: ', file);
// console.log('file: ', file);
const reader = new FileReader();
reader.readAsText(file);
reader.onload = async (result) => {
@ -330,7 +311,7 @@ const getActions = (
if (!data) return [];
const actions = [
{
key: 'edit',
key: 'update',
text: '编辑',
tooltip: {
title: '编辑',
@ -355,7 +336,7 @@ const getActions = (
},
},
{
key: 'debug',
key: 'export',
text: '导出',
tooltip: {
title: '导出',
@ -366,7 +347,7 @@ const getActions = (
},
},
{
key: 'debug',
key: 'log',
text: '通知记录',
tooltip: {
title: '通知记录',

View File

@ -53,9 +53,9 @@
<script setup lang='ts' name='AddModel'>
import type { PropType } from 'vue'
import type { metadataType, TriggerDevice, TriggerDeviceOptions } from '@/views/rule-engine/Scene/typings'
import type { metadataType, TriggerDevice, TriggerDeviceOptions, SelectorValuesItem } from '@/views/rule-engine/Scene/typings'
import { onlyMessage } from '@/utils/comm'
import { detail as deviceDetail } from '@/api/device/instance'
import { detail as deviceDetail } from '@/api/device/instance'
import Product from './Product.vue'
import DeviceSelect from './DeviceSelect.vue'
import Type from './Type.vue'
@ -66,12 +66,13 @@ type Emit = {
(e: 'save', data: TriggerDevice, options: Record<string, any>): void
}
interface AddModelType extends Omit<TriggerDevice, 'selectorValues'> {
stepNumber: number
deviceKeys: Array<{ label: string, value: string }>
orgId: Array<{ label: string, value: string }>
deviceKeys: SelectorValuesItem[]
orgId: SelectorValuesItem[]
productDetail: any
selectorValues: Array<Record<string, any>>
selectorValues: SelectorValuesItem[]
metadata: metadataType,
operator: TriggerDeviceOptions
}
@ -95,23 +96,21 @@ const props = defineProps({
})
const addModel = reactive<AddModelType>({
productId: '',
selector: 'fixed',
selectorValues: [],
productId: props.value.productId || '',
selector: props.value.selector || 'fixed',
selectorValues: props.value.selectorValues || [],
stepNumber: 0,
deviceKeys: [],
orgId: [],
deviceKeys: props.value.selectorValues || [],
orgId: props.value.selectorValues || [],
productDetail: {},
metadata: {},
operator: {
operator: props.value.operation || {
operator: 'online'
}
})
const optionsCache = ref(props.options)
Object.assign(addModel, props.value)
const handleOptions = (data: TriggerDeviceOptions) => {
const typeIconMap = {
writeProperty: 'icon-bianji1',
@ -230,6 +229,11 @@ const productChange = () => {
addModel.selectorValues = []
}
const getDeviceDetailByMetadata = async (deviceId: string) => {
const resp = await deviceDetail(deviceId)
return resp.result?.metadata
}
const save = async (step?: number) => {
let _step = step !== undefined ? step : addModel.stepNumber
if (_step === 0) {
@ -240,12 +244,8 @@ const save = async (step?: number) => {
return onlyMessage(isFixed ? '请选择设备' : '请选择部门', 'error')
}
//
if (isFixed && addModel.selectorValues?.length === 1) {
const resp = await deviceDetail(addModel.selectorValues[0].value)
handleMetadata(resp.result.metadata)
} else {
handleMetadata(addModel.productDetail?.metadata)
}
const onlyOneDevice = isFixed && addModel.selectorValues?.length === 1
handleMetadata( onlyOneDevice ? await getDeviceDetailByMetadata(addModel.selectorValues[0].value) : addModel.productDetail?.metadata)
addModel.stepNumber = 2
} else {
const typeData = await typeRef.value.vail()
@ -273,6 +273,16 @@ const stepChange = (step: number) => {
}
}
const initQuery = async () => {
if (props.value.selector === 'fixed' && props.value.selectorValues?.length) {
handleMetadata(await getDeviceDetailByMetadata(props.value.selectorValues[0].value))
}
}
nextTick(() => {
initQuery()
})
</script>
<style scoped>

View File

@ -1,5 +1,5 @@
<template>
<Search
<j-advanced-search
:columns="columns"
type='simple'
@search="handleSearch"
@ -7,7 +7,7 @@
target="scene-triggrt-device-device"
/>
<a-divider style='margin: 0' />
<j-table
<j-pro-table
ref='actionRef'
model='CARD'
:columns='columns'
@ -60,7 +60,7 @@
</template>
</CardBox>
</template>
</j-table>
</j-pro-table>
</template>
<script setup lang='ts' name='DeviceSelectList'>
@ -68,17 +68,17 @@ import type { PropType } from 'vue'
import { getImage } from '@/utils/comm'
import { query } from '@/api/device/instance'
import { cloneDeep } from 'lodash-es'
import type { SelectorValuesItem } from '@/views/rule-engine/Scene/typings'
type Emit = {
(e: 'update', data: Array<{ name: string, value: string}>): void
(e: 'update', data: SelectorValuesItem[]): void
}
const actionRef = ref()
const params = ref({})
const context = inject('SceneDeviceAddModel')
const props = defineProps({
rowKeys: {
type: Array as PropType<Array<{ name: string, value: string}>>,
type: Array as PropType<SelectorValuesItem[]>,
default: () => ([])
},
productId: {

View File

@ -12,17 +12,13 @@ import DeviceList from './DeviceList.vue'
import OrgList from './OrgList.vue'
import { getImage } from '@/utils/comm'
import type { PropType } from 'vue'
type ItemType = {
name: string,
value: string
}
import { SelectorValuesItem } from '@/views/rule-engine/Scene/typings'
type Emit = {
(e: 'update:selector', data: string): void
(e: 'update:selectorValues', data: ItemType[]): void
(e: 'update:deviceKeys', data: ItemType[]): void
(e: 'update:orgId', data: ItemType[]): void
(e: 'update:selectorValues', data: SelectorValuesItem[]): void
(e: 'update:deviceKeys', data: SelectorValuesItem[]): void
(e: 'update:orgId', data: SelectorValuesItem[]): void
}
const emit = defineEmits<Emit>()
@ -36,18 +32,22 @@ const props = defineProps({
type: String,
default: ''
},
device: {
type: Array as PropType<ItemType[]>,
selectorValues: {
type: Array as PropType<SelectorValuesItem[]>,
default: () => []
},
deviceKeys: {
type: Array as PropType<SelectorValuesItem[]>,
default: () => []
},
orgId: {
type: Array as PropType<ItemType[]>,
type: Array as PropType<SelectorValuesItem[]>,
default: () => []
}
})
const selectorModel = ref(props.selector)
const devices = ref(props.device)
const devices = ref(props.deviceKeys)
const orgIds = ref(props.orgId)
const typeList = [
@ -69,6 +69,7 @@ const updateDevice = (d: any[]) => {
}
const updateOrg = (d: any[]) => {
console.log('updateOrg', d)
orgIds.value = d
emit('update:orgId', d)
emit('update:selectorValues', d)

View File

@ -1,5 +1,5 @@
<template>
<Search
<j-advanced-search
:columns="columns"
type='simple'
@search="handleSearch"
@ -7,7 +7,7 @@
target="scene-triggrt-device-category"
/>
<a-divider style='margin: 0' />
<JTable
<j-pro-table
ref="instanceRef"
model='TABLE'
type='TREE'
@ -26,9 +26,10 @@
onChange: selectedRowChange
}'
:onChange='tableChange'
@selectCancel='cancelAll'
>
</JTable>
</j-pro-table>
</template>
@ -36,14 +37,15 @@
import type { PropType } from 'vue'
import { getExpandedRowById } from './util'
import { getTreeData_api } from '@/api/system/department'
import { SelectorValuesItem } from '@/views/rule-engine/Scene/typings'
type Emit = {
(e: 'update', data: Array<{ name: string, value: string}>): void
(e: 'update', data: SelectorValuesItem[]): void
}
const props = defineProps({
rowKeys: {
type: Array as PropType<Array<{ name: string, value: string}>>,
type: Array as PropType<SelectorValuesItem[]>,
default: () => ([])
},
productId: {
@ -69,6 +71,9 @@ const columns = [
width: 300,
ellipsis: true,
dataIndex: 'name',
search: {
type: 'string'
}
},
{
title: '排序',
@ -109,10 +114,14 @@ const query = async (p: any) => {
return resp
}
const selectedRowChange = (_: any, selectedRows: any[]) => {
const selectedRowChange = (values: any, selectedRows: any[]) => {
const item = selectedRows[0]
console.log(selectedRows)
emit('update', item ? [{ name: item.name, value: item.id }] : [])
console.log(values, selectedRows)
emit('update', [{ name: item.name, value: item.id }])
}
const cancelAll = () => {
emit('update', [])
}
const expandedRowChange = (keys: string[]) => {

View File

@ -1,5 +1,5 @@
<template>
<Search
<j-advanced-search
:columns="columns"
type='simple'
@search="handleSearch"
@ -7,7 +7,7 @@
target="scene-triggrt-device-device"
/>
<a-divider style='margin: 0' />
<j-table
<j-pro-table
ref='actionRef'
model='CARD'
:columns='columns'
@ -53,7 +53,7 @@
</template>
</CardBox>
</template>
</j-table>
</j-pro-table>
</template>
<script setup lang='ts' name='Product'>
@ -83,6 +83,7 @@ const props = defineProps({
})
const emit = defineEmits<Emit>()
const firstFind = ref(true)
const columns = [
{
@ -230,7 +231,7 @@ const handleSearch = (p: any) => {
params.value = p
}
const productQuery = (p: any) => {
const productQuery = async (p: any) => {
const sorts: any = [];
if (props.rowKey) {
@ -241,7 +242,15 @@ const productQuery = (p: any) => {
}
sorts.push({ name: 'createTime', order: 'desc' });
p.sorts = sorts
return queryProductList(p)
const resp = await queryProductList(p)
if (resp.success && props.rowKey && firstFind.value) {
const productItem = (resp.result as { data: any[]}).data.find((item: any) => item.id === props.rowKey)
emit('update:detail', productItem)
firstFind.value = false
}
return {
...resp
}
}
const handleClick = (detail: any) => {

View File

@ -14,6 +14,7 @@
<Title :options='data.options.trigger' />
</AddButton>
</a-form-item>
<Terms />
<Action />
<AddModel v-if='visible' @cancel='visible = false' @save='save' :value='data.trigger.device' :options='data.options.trigger' />
</div>
@ -26,6 +27,7 @@ import AddModel from './AddModal.vue'
import AddButton from '../components/AddButton.vue'
import Title from '../components/Title.vue'
import Action from '../action/index.vue'
import Terms from '../components/Terms'
import type { TriggerDevice } from '@/views/rule-engine/Scene/typings'
const sceneStore = useSceneStore()

View File

@ -0,0 +1,153 @@
<template>
<j-dropdown class='scene-select' trigger='click'>
<div :class='dropdownButtonClass'>
<span :style='LabelStyle'>
{{ label }}
</span>
</div>
<template #overlay>
<template v-if='options.length'>
<j-menu v-if='component === "select"' @click='menuSelect'>
<j-menu-item v-for='item in options' :key='item.value'>{{ item.label }}</j-menu-item>
</j-menu>
<j-tree
:selectedKeys='selectValue ? [selectValue] : []'
:treeData='options'
@select='treeSelect'
/>
</template>
<div class='scene-select-empty' v-else>
<j-empty />
</div>
</template>
</j-dropdown>
</template>
<script lang='ts' setup name='DropdownButton'>
import type { PropType } from 'vue'
type LabelType = string | number | undefined
type DropdownButtonOptions = {
label: string;
value: string;
children?: DropdownButtonOptions[];
[key: string]: any;
};
type Emit = {
(e: 'update:value', data: string | number): void
(e: 'select', data: DropdownButtonOptions | undefined ): void
}
const props = defineProps({
placeholder: {
type: String,
default: undefined
},
value: {
type: [String, Number],
default: undefined
},
options: {
type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => []
},
type: {
type: String,
default: 'column' // 'column' | 'termType' | 'value' | 'type'
},
component: {
type: String,
default: 'select' // 'select' | 'treeSelect'
}
})
const emit = defineEmits<Emit>()
const label = ref<LabelType>(props.placeholder)
const selectValue = ref(props.value)
const flatMapTree = new Map()
const LabelStyle = computed(() => {
return {
color: selectValue.value ? '#' : '#'
}
})
const dropdownButtonClass = computed(() => ({
'dropdown-button': true,
'column': props.type === 'column',
'termType': props.type === 'termType',
'value': props.type === 'value',
'type': props.type === 'type',
}))
const getOption = (key?: string | number): DropdownButtonOptions | undefined => {
let option
for(let i = props.options.length - 1; i >= 0; i --) {
const cacheOption = props.options[i]
if (cacheOption.value === key) {
option = {
...cacheOption
}
break
}
}
return option
}
const treeSelect = () => {
}
const menuSelect = (v: any) => {
const option = getOption(props.value)
emit('update:value', v.key)
emit('select', option)
}
watch([props.options, props.value], () => {
const option = getOption(props.value)
console.log(props.value)
if (option) {
label.value = option.label
} else {
label.value = props.value || props.placeholder
}
}, { immediate: true })
</script>
<style scoped lang='less'>
.dropdown-button {
display: flex;
align-items: center;
padding: 6px 8px;
border: 1px solid #d9d9d9;
border-radius: 8px;
cursor: pointer;
}
.column {
color: #00a4fe;
background-color: rgba(154, 219, 255, 0.3);
border-color: rgba(0, 164, 254, 0.3);
}
.termType {
color: #2f54eb;
background-color: rgba(163, 202, 255, 0.3);
border-color: rgba(47, 84, 235, 0.3);
}
.value {
color: #692ca7;
background-color: rgba(188, 125, 238, 0.1);
border-color: rgba(188, 125, 238, 0.5);
}
.type {
padding: 5px 10px;
}
</style>

View File

@ -0,0 +1,13 @@
<template>
</template>
<script>
export default {
name: 'ParamsDropdown'
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,135 @@
<template>
<div :class='["actions-terms-warp", props.class]'>
<div class='actions-terms-title'>
{{ isFirst ? '当' : '否则' }}
</div>
<div :class='optionsClass'>
<j-popconfirm
title='确认删除?'
@confirm='onDelete'
>
<div v-if='!isFirst' class='terms-params-delete danger show'>
<AIcon type='DeleteOutlined' />
</div>
</j-popconfirm>
<div
class='actions-terms-list'
@mouseover='mouseover'
@mouseout='mouseout'
>
<j-popconfirm
title='该操作将清空其它所有否则条件,确认删除?'
placement='topRight'
@confirm='onDeleteAll'
>
<AIcon type='CloseOutlined' v-show='showDelete' />
</j-popconfirm>
<div class='actions-terms-list-content'>
<template v-if='showWhen'>
<TermsItem
v-for='(item, index) in data.when'
:key='item.key'
:isFirst='index === 0'
:isLast='index === data.when.length -1'
:branchName='name'
:whenName='index'
:data='item'
:isFrist='index === 0'
/>
</template>
<span v-else class='when-add' @click='addWhen' :style='{ padding: isFirst ? "16px 0" : 0 }'>
<AIcon type='PlusCircleOutlined' style='padding: 4px' />
添加过滤条件
</span>
</div>
</div>
<div class='actions-branches'>
<j-form-item></j-form-item>
</div>
</div>
</div>
</template>
<script lang='ts' setup name='Branchs'>
import type { PropType } from 'vue'
import type { ActionBranchesProps } from '@/views/rule-engine/Scene/typings'
import TermsItem from './TermsItem.vue'
import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene'
const sceneStore = useSceneStore()
const { data: FormModel } = storeToRefs(sceneStore)
const props = defineProps({
isFirst: {
type: Boolean,
default: true
},
data: {
type: Object as PropType<ActionBranchesProps>,
default: () => ({
when: [],
shakeLimit: {},
then: []
})
},
class: {
type: String,
default: ''
},
name: {
type: Number,
default: 0
}
})
const showDelete = ref(false)
const error = ref(false)
const showWhen = computed(() => {
return props.data.when.length
})
const onDelete = () => {
}
const onDeleteAll = () => {
}
const mouseover = () => {
if (props.isFirst && props.data.when.length){
showDelete.value = true
}
}
const mouseout = () => {
if (props.isFirst && props.data.when.length){
showDelete.value = false
}
}
const addWhen = () => {
}
const optionsClass = computed(() => {
return {
'actions-terms-options': true,
border: !props.isFirst,
error: error
}
})
</script>
<style scoped lang='less'>
.when-add {
font-size: 14px;
color: #2F54EB;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,142 @@
<template>
<div class='terms-params-item'>
<div v-if='!isFirst' class='term-type-warp'>
<DropdownButton
:options='[
{ label: "并且", value: "and" },
{ label: "或者", value: "or" },
]'
type='type'
v-model:value='paramsValue.type'
/>
</div>
<div
class='params-item_button'
@mouseover='mouseover'
@mouseout='mouseout'
>
<DropdownButton
:options='options'
type='column'
placeholder='请选择参数'
v-model:value='paramsValue.column'
component='treeSelect'
@select='columnSelect'
/>
<DropdownButton
:options='termTypeOptions'
type="termType"
placeholder="操作符"
v-model:value='paramsValue.termsType'
@select='termsTypeSelect'
/>
<termplate v-if='showDouble'>
</termplate>
<j-popconfirm title='确认删除?' @confirm='onDelete'>
<div v-show='showDelete' class='button-delete'> <AIcon type='CloseOutlined' /></div>
</j-popconfirm>
</div>
<div class='term-add' @click.stop='termAdd'>
<div class='terms-content'>
<AIcon type='PlusOutlined' style='font-size: 12px' />
</div>
</div>
</div>
</template>
<script setup lang='ts' name='ParamsItem'>
import type { PropType } from 'vue'
import type { TermsType } from '@/views/rule-engine/Scene/typings'
import DropdownButton from '../DropdownButton.vue'
import { inject } from 'vue'
import { ContextKey } from './util'
const props = defineProps({
isFirst: {
type: Boolean,
default: true
},
isLast: {
type: Boolean,
default: true
},
name: {
type: Number,
default: 0
},
value: {
type: Object as PropType<TermsType>,
default: () => ({
column: '',
type: '',
termsType: undefined,
value: undefined
})
}
})
const paramsValue = reactive<TermsType>({
column: '',
type: '',
termType: undefined,
value: undefined
})
const showDelete = ref(false)
const columnOptions = inject(ContextKey)
const options = computed(() => {
function handleOptions() {
}
return []
})
const termTypeOptions = computed(() => {
return []
})
const showDouble = computed(() => {
return paramsValue.termType ? ['nbtw', 'btw', 'in', 'nin'].includes(paramsValue.termType) : false
})
const mouseover = () => {
if (props.name !== 0){
showDelete.value = true
}
}
const mouseout = () => {
if (props.name !== 0){
showDelete.value = false
}
}
const columnSelect = () => {
}
const termsTypeSelect = () => {
}
const termAdd = () => {
}
const onDelete = () => {
}
onMounted(() => {
Object.assign(paramsValue, props.value)
})
</script>
<style scoped>
</style>

View File

@ -0,0 +1,121 @@
<template>
<div class='actions-terms'>
<TitleComponent data='触发条件' style='font-size: 14px;' >
<template #extra>
<j-switch
:checked='open'
@change='change'
checkedChildren='开'
unCheckedChildren='关'
style='margin-left: 4px;'
/>
</template>
</TitleComponent>
<template v-if='open'>
<template v-for='(item, index) in data.branches'>
<Branches
v-if='!!item'
:data='item'
:isFirst='index === 0'
:name='index'
:key='item.key'
@delete='branchesDelete'
@deleteAll='branchesDeleteAll'
/>
<div v-else class='actions-terms-warp' :style='{ marginTop: data.branches.length === 2 ? 0 : 24 }'>
<div class='actions-terms-title' style='padding: 0'>
否则
</div>
<div class='actions-terms-options no-when'>
<AIcon type='PlusOutlined' class='when-add-button' @click='addBranches' />
</div>
</div>
</template>
</template>
<j-form-item
v-else
:name='["branches", 0, "then"]'
:rules='rules'
>
</j-form-item>
</div>
</template>
<script setup lang='ts' name='Terms'>
import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene'
import { cloneDeep } from 'lodash-es'
import { provide } from 'vue'
import { ContextKey } from './util'
import { getParseTerm } from '@/api/rule-engine/scene'
import type { FormModelType } from '@/views/rule-engine/Scene/typings'
import Branches from './Branchs.vue'
const sceneStore = useSceneStore()
const { data } = storeToRefs(sceneStore)
const open = ref(false)
const columnOptions = ref<any[]>([])
provide(ContextKey, columnOptions)
const change = (e: boolean) => {
open.value = e
}
const rules = [{
validator(_: string, value: any) {
if (!value || (value && !value.length)) {
return Promise.reject('至少配置一个执行动作')
} else {
const isActions = value.some((item: any) => item.actions && item.actions.length)
return isActions ? Promise.resolve() : Promise.reject('至少配置一个执行动作');
}
return Promise.resolve();
}
}]
const queryColumn = (dataModel: FormModelType) => {
const cloneDevice = cloneDeep(dataModel)
cloneDevice.branches = cloneDevice.branches?.filter(item => !!item)
getParseTerm(cloneDevice).then(res => {
columnOptions.value = res as any
})
}
const addBranches = () => {
}
const branchesDelete = (index: number) => {
if ((data as FormModelType).branches?.length === 2) {
(data as FormModelType).branches?.splice(index, 1, null as any)
} else {
(data as FormModelType).branches?.splice(index, 1)
}
(data as FormModelType).options?.when?.splice(index, 1)
}
const branchesDeleteAll = () => {
}
watchEffect(() => {
if ((data as FormModelType).trigger?.device) {
queryColumn((data as FormModelType))
}
})
watchEffect(() => {
open.value = !(
(data as FormModelType).branches &&
(data as FormModelType).branches?.length === 1
)
})
</script>
<style scoped lang='less'>
</style>

View File

@ -0,0 +1,126 @@
<template>
<div class='terms-params'>
<div class='terms-params-warp'>
<div v-if='!isFirst' class='term-type-warp'>
<DropdownButton
:options='[
{ label: "并且", value: "and" },
{ label: "或者", value: "or" },
]'
type='type'
v-model:value='formModel.branches[branchName].when[whenName].type'
/>
</div>
<div
class='terms-params-content'
@mouseover='mouseover'
@mouseout='mouseout'
>
<j-popconfirm
title='确认删除?'
@confirm='onDelete'
>
<div v-show='showDelete' class='terms-params-delete'>
<AIcon type='CloseOutlined' />
</div>
</j-popconfirm>
<j-form-item
v-for='(item, index) in data.terms'
:key='item.key'
:name='["branches", branchName, "when", whenName, "terms", index]'
>
<ParamsItem
v-model:value='formModel.branches[branchName].when[whenName].terms[index]'
:isFirst='index === 0'
:isLast='index === data.terms.length - 1'
:name='index'
@change='paramsChange'
@delete='paramsDelete'
@add='paramsAdd'
/>
</j-form-item>
</div>
</div>
</div>
</template>
<script setup lang='ts' name='TermsItem'>
import type { PropType } from 'vue'
import type { TermsType } from '@/views/rule-engine/Scene/typings'
import DropdownButton from '../DropdownButton.vue'
import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene'
import ParamsItem from './ParamsItem.vue'
const sceneStore = useSceneStore()
const { data: formModel } = storeToRefs(sceneStore)
const props = defineProps({
isFirst: {
type: Boolean,
default: true
},
data: {
type: Object as PropType<TermsType>,
default: () => ({
when: [],
shakeLimit: {},
then: []
})
},
class: {
type: String,
default: ''
},
branchName: {
type: Number,
default: 0
},
whenName: {
type: Number,
default: 0
}
})
const showDelete = ref(false)
const mouseover = () => {
console.log(props.whenName)
if (props.whenName !== 0){
showDelete.value = true
}
}
const mouseout = () => {
console.log(props.whenName)
if (props.whenName !== 0){
showDelete.value = false
}
}
const onDelete = () => {
}
const onDeleteAll = () => {
}
const paramsChange = () => {
}
const paramsDelete = () => {
}
const paramsAdd = () => {
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,204 @@
.add-button() {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
color: rgba(0, 0, 0, 0.3);
background-color: #fff;
border: 1px dashed rgba(0, 0, 0, 0.3);
border-radius: 50%;
cursor: pointer;
}
.deleteBtn() {
position: absolute;
top: -10px;
right: -10px;
display: none;
width: 20px;
height: 20px;
color: #999;
line-height: 20px;
text-align: center;
background-color: #f1f1f1;
border-radius: 50%;
cursor: pointer;
&.show {
display: block;
}
&:hover {
background-color: #f3f3f3;
}
}
.actions-terms {
.actions-terms-warp {
display: flex;
width: 66.66%;
margin-bottom: 24px;
&.first-children {
width: 100%;
.actions-branches {
width: 66.66%;
}
}
&.first-children,
&:last-child {
margin-bottom: 0;
}
.when-add-button {
.add-button();
}
.actions-terms-title {
width: 40px;
padding-top: 16px;
color: #6968be;
font-weight: 800;
font-size: 16px;
}
.actions-terms-options {
position: relative;
display: flex;
flex-direction: column;
flex-grow: 1;
width: 0;
&.border {
padding: 10px 18px 0 18px;
border: 1px dashed #999;
border-radius: 2px;
}
&.no-when {
flex: none;
}
.actions-terms-list {
position: relative;
margin-bottom: 16px;
.ant-form-item-has-error {
.params-item_button {
border-color: @error-color;
}
}
.actions-terms-list-content {
display: flex;
padding-top: 10px;
overflow-x: auto;
overflow-y: visible;
row-gap: 16px;
}
}
}
}
}
.terms-params {
display: flex;
flex-shrink: 0;
.terms-params-warp {
display: flex;
align-items: baseline;
}
.terms-params-content {
position: relative;
display: flex;
// flex-wrap: wrap;
padding: 8px;
padding-bottom: 0;
border: 1px dashed #e0e0e0;
//background-color: #fafafa;
border-radius: 6px;
row-gap: 16px;
.terms-params-item {
display: flex;
align-items: center;
}
.ant-form-item {
margin-bottom: 8px;
&:not(:first-child) {
.ant-form-item-explain-error {
padding-left: 80px !important;
}
}
}
}
.terms-group-add {
width: 66px;
margin-left: 16px;
padding: 2px 8px;
color: rgba(0, 0, 0, 0.3);
background: #fff;
border: 1px dashed rgba(0, 0, 0, 0.3);
border-radius: 30px;
cursor: pointer;
.terms-content {
display: flex;
align-items: center;
}
}
.term-type-warp {
width: 50px;
margin: 0 16px;
.term-type {
padding-top: 4px;
padding-bottom: 4px;
border-radius: 2px;
}
}
}
.terms-params-item {
.params-button {
padding: 6px 8px;
border: 1px solid #d9d9d9;
border-radius: 8px;
cursor: pointer;
}
.params-item_button {
position: relative;
display: flex;
gap: 2px;
.button-delete {
.deleteBtn();
}
}
.term-add {
margin-left: 16px;
.add-button();
}
}
.terms-params-delete {
.deleteBtn();
&.danger {
color: #e50012;
background-color: rgba(229, 0, 18, 0.1);
}
&.filter-terms-params-delete {
transform: translateY(6px);
}
}

View File

@ -0,0 +1,4 @@
import Terms from './Terms.vue'
import './index.less'
export default Terms

View File

@ -0,0 +1,2 @@
export const ContextKey = 'columnOptions'

View File

@ -122,13 +122,18 @@ export interface TriggerDeviceOptions {
functionParameters?: Record<string, any>[];
}
export type SelectorValuesItem = {
name: string
value: any
}
/**
*
*/
export interface TriggerDevice {
productId: string;
selector: string;
selectorValues?: Record<string, any>[];
selectorValues?: SelectorValuesItem[];
operation?: TriggerDeviceOptions;
}

View File

@ -1,288 +1,291 @@
<template>
<page-container>
<a-card class="basis-container">
<a-form
layout="vertical"
ref="formBasicRef"
:rules="rulesFrom"
:model="formValue"
>
<a-row :span="24" :gutter="24">
<a-col :span="10">
<a-form-item label="系统名称" name="title">
<a-input
v-model:value="formValue.title"
:maxlength="64"
placeholder="请输入系统名称"
/>
</a-form-item>
<a-form-item label="主题色" name="headerTheme">
<a-select v-model:value="formValue.headerTheme">
<a-select-option value="light"
>白色</a-select-option
>
<a-select-option value="dark">黑色</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template #label>
<span>高德API Key</span>
<a-tooltip title="配置后平台可调用高德地图GIS服务">
<img
class="img-style"
:src="getImage('/init-home/mark.png')"
/>
</a-tooltip>
</template>
<a-input
v-model:value="formValue.apiKey"
placeholder="请输入高德API Key"
/>
</a-form-item>
<a-form-item name="'base-path'">
<template #label>
<span>base-path</span>
<a-tooltip title="系统后台访问的url">
<img
class="img-style"
:src="getImage('/init-home/mark.png')"
/>
</a-tooltip>
</template>
<a-input
v-model:value="formValue['base-path']"
placeholder="请输入高德API Key"
/>
</a-form-item>
<a-row :gutter="24" :span="24">
<a-col>
<a-form-item label="系统logo">
<div class="upload-image-warp-logo">
<div class="upload-image-border-logo">
<a-upload
name="file"
:action="action"
:headers="headers"
:showUploadList="false"
:beforeUpload="
uploader.beforeLogoUpload
"
@change="uploader.handleChangeLogo"
:accept="
uploader.imageTypes.toString()
"
>
<div
class="upload-image-content-logo"
<page-container>
<div class="basis-container">
<j-form
layout="vertical"
ref="formBasicRef"
:rules="rulesFrom"
:model="formValue"
>
<j-row :span="24" :gutter="24">
<j-col :span="10">
<j-form-item label="系统名称" name="title">
<j-input
v-model:value="formValue.title"
:maxlength="64"
placeholder="请输入系统名称"
/>
</j-form-item>
<j-form-item label="主题色" name="headerTheme">
<j-select v-model:value="formValue.headerTheme">
<j-select-option value="light">
白色
</j-select-option>
<j-select-option value="dark">
黑色
</j-select-option>
</j-select>
</j-form-item>
<j-form-item>
<template #label>
<span>高德API Key</span>
<j-tooltip
title="配置后平台可调用高德地图GIS服务"
>
<img
class="img-style"
:src="getImage('/init-home/mark.png')"
/>
</j-tooltip>
</template>
<j-input
v-model:value="formValue.apiKey"
placeholder="请输入高德API Key"
/>
</j-form-item>
<j-form-item name="'base-path'">
<template #label>
<span>base-path</span>
<j-tooltip title="系统后台访问的url">
<img
class="img-style"
:src="getImage('/init-home/mark.png')"
/>
</j-tooltip>
</template>
<j-input
v-model:value="formValue['base-path']"
placeholder="请输入高德API Key"
/>
</j-form-item>
<j-row :gutter="24" :span="24">
<j-col>
<j-form-item label="系统logo">
<div class="upload-image-warp-logo">
<div class="upload-image-border-logo">
<a-upload
name="file"
:action="action"
:headers="headers"
:showUploadList="false"
:beforeUpload="
uploader.beforeLogoUpload
"
@change="
uploader.handleChangeLogo
"
:accept="
uploader.imageTypes.toString()
"
>
<div
class="loading-logo"
v-if="form.logoLoading"
class="upload-image-content-logo"
>
<LoadingOutlined
style="font-size: 28px"
/>
</div>
<div
class="upload-image"
style="height: 100%"
v-if="formValue.logo"
:style="
formValue.logo
? `background-image: url(${formValue.logo});`
: ''
"
></div>
<div
v-if="formValue.logo"
class="upload-image-mask"
>
点击修改
</div>
<div v-else>
<div
class="loading-logo"
v-if="form.logoLoading"
>
<LoadingOutlined
style="
font-size: 28px;
"
<AIcon
type="LoadingOutlined"
/>
</div>
<div
class="upload-image"
style="height: 100%"
v-if="formValue.logo"
:style="
formValue.logo
? `background-image: url(${formValue.logo});`
: ''
"
></div>
<div
v-if="formValue.logo"
class="upload-image-mask"
>
点击修改
</div>
<div v-else>
<PlusOutlined
style="
font-size: 28px;
<AIcon
:type="
form.logoLoading
? 'LoadingOutlined'
: 'PlusOutlined'
"
/>
</div>
</div>
</div>
</a-upload>
<div v-if="form.logoLoading">
<div class="upload-loading-mask">
<LoadingOutlined
style="font-size: 28px"
/>
</div>
</div>
</div>
</div>
<div class="upload-tips">推荐尺寸200*200</div>
<div class="upload-tips">支持jpg,png</div>
</a-form-item>
</a-col>
<a-col>
<a-form-item>
<template #label>
<span>浏览器页签</span>
<a-tooltip
title="浏览器tab页中显示的图片元素"
>
<img
class="img-style"
:src="
getImage('/init-home/mark.png')
"
/>
</a-tooltip>
</template>
<div class="upload-image-warp-logo">
<div class="upload-image-border-logo">
<a-upload
name="file"
:action="action"
:headers="headers"
:showUploadList="false"
:beforeUpload="
uploader.beforeIconUpload
"
@change="uploader.changeIconUpload"
:accept="
uploader.imageTypes.toString()
"
>
<div
class="upload-image-content-logo"
>
</a-upload>
<div v-if="form.logoLoading">
<div
v-if="form.iconLoading"
class="loading-icon"
class="upload-loading-mask"
>
<LoadingOutlined
style="font-size: 28px"
<AIcon
type="LoadingOutlined"
/>
</div>
</div>
</div>
</div>
<div class="upload-tips">
推荐尺寸200*200
</div>
<div class="upload-tips">支持jpg,png</div>
</j-form-item>
</j-col>
<j-col>
<j-form-item>
<template #label>
<span>浏览器页签</span>
<j-tooltip
title="浏览器tab页中显示的图片元素"
>
<img
class="img-style"
:src="
getImage(
'/init-home/mark.png',
)
"
/>
</j-tooltip>
</template>
<div class="upload-image-warp-logo">
<div class="upload-image-border-logo">
<a-upload
name="file"
:action="action"
:headers="headers"
:showUploadList="false"
:beforeUpload="
uploader.beforeIconUpload
"
@change="
uploader.changeIconUpload
"
:accept="
uploader.imageTypes.toString()
"
>
<div
class="upload-image-icon"
v-if="formValue.ico"
:style="
formValue.ico
? `background-image: url(${formValue.ico});`
: ''
"
></div>
<div
v-if="formValue.ico"
class="upload-image-mask"
class="upload-image-content-logo"
>
点击修改
</div>
<div v-else>
<div>
<PlusOutlined
style="
font-size: 28px;
"
<div
v-if="form.iconLoading"
class="loading-icon"
>
<AIcon
type="LoadingOutlined"
/>
</div>
<div
class="upload-image-icon"
v-if="formValue.ico"
:style="
formValue.ico
? `background-image: url(${formValue.ico});`
: ''
"
></div>
<div
v-if="formValue.ico"
class="upload-image-mask"
>
点击修改
</div>
<div v-else>
<div>
<AIcon
type="PlusOutlined"
/>
</div>
</div>
</div>
</a-upload>
</div>
</div>
<div class="upload-tips">推荐尺寸64*64</div>
<div class="upload-tips">支持icon格式</div>
</j-form-item>
</j-col>
</j-row>
</j-col>
<j-col :span="14">
<j-form-item label="登录背景图">
<div class="upload-image-warp-back">
<div class="upload-image-border-back">
<a-upload
name="file"
:action="action"
:headers="headers"
:beforeUpload="
uploader.beforeBackUpload
"
:showUploadList="false"
@change="uploader.changeBackUpload"
:accept="uploader.imageTypes.toString()"
>
<div class="upload-image-content-back">
<div
v-if="form.backLoading"
class="loading-back"
>
<AIcon type="LoadingOutlined" />
</div>
<div
class="upload-image"
v-if="formValue.backgroud"
:style="
formValue.backgroud
? `background-image: url(${formValue.backgroud});`
: ''
"
></div>
<div
v-if="formValue.backgroud"
class="upload-image-mask"
>
点击修改
</div>
<div v-else>
<div>
<AIcon
type="PlusOutlined"
/>
</div>
</div>
</a-upload>
</div>
</div>
</a-upload>
</div>
<div class="upload-tips">推荐尺寸64*64</div>
<div class="upload-tips">支持icon格式</div>
</a-form-item>
</a-col>
</a-row>
</a-col>
<a-col :span="14">
<a-form-item label="登录背景图">
<div class="upload-image-warp-back">
<div class="upload-image-border-back">
<a-upload
name="file"
:action="action"
:headers="headers"
:beforeUpload="uploader.beforeBackUpload"
:showUploadList="false"
@change="uploader.changeBackUpload"
:accept="uploader.imageTypes.toString()"
>
<div class="upload-image-content-back">
<div
v-if="form.backLoading"
class="loading-back"
>
<LoadingOutlined
style="font-size: 28px"
/>
</div>
<div
class="upload-image"
v-if="formValue.backgroud"
:style="
formValue.backgroud
? `background-image: url(${formValue.backgroud});`
: ''
"
></div>
<div
v-if="formValue.backgroud"
class="upload-image-mask"
>
点击修改
</div>
<div v-else>
<div>
<PlusOutlined
style="font-size: 28px"
/>
</div>
</div>
</div>
</a-upload>
</div>
</div>
<div class="upload-tips">
支持4M以内的图片:支持jpgpng
</div>
<div class="upload-tips">建议尺寸1400x1080</div>
</a-form-item>
</a-col>
</a-row>
</a-form>
<div class="upload-tips">
支持4M以内的图片:支持jpgpng
</div>
<div class="upload-tips">建议尺寸1400x1080</div>
</j-form-item>
</j-col>
</j-row>
</j-form>
<a-button
type="primary"
@click="form.clickSave"
:disabled="
form.saveLoading ||
form.logoLoading ||
form.iconLoading ||
form.backLoading
"
>保存</a-button
>
</a-card>
</page-container>
<j-button
type="primary"
@click="form.clickSave"
:disabled="
form.saveLoading ||
form.logoLoading ||
form.iconLoading ||
form.backLoading
"
>
保存
</j-button>
</div>
</page-container>
</template>
<script setup lang="ts" name="Basis">
import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
import { formType, uploaderType } from './index';
import { getImage } from '@/utils/comm.ts';
import { message } from 'ant-design-vue';
@ -365,8 +368,8 @@ const form = reactive<formType>({
},
clickSave: () => {
const hasPermission = usePermissionStore().hasPermission;
if(hasPermission(`system/Basis:update`) ){
formBasicRef.value.validate().then(() => {
if (hasPermission(`system/Basis:update`)) {
formBasicRef.value.validate().then(() => {
form.saveLoading = true;
const params = [
{
@ -400,11 +403,9 @@ const form = reactive<formType>({
})
.finally(() => (form.saveLoading = false));
});
}else {
message.warning('暂无权限,请联系管理员');
}
} else {
message.warning('暂无权限,请联系管理员');
}
},
});
const { formValue, rulesFrom } = toRefs(form);
@ -502,6 +503,8 @@ form.getDetails();
<style lang="less" scoped>
.basis-container {
padding: 24px;
background-color: #fff;
.img-style {
width: 16px;
height: 16px;
@ -625,5 +628,9 @@ form.getDetails();
font-size: 14px;
line-height: 1.5715;
}
.anticon {
font-size: 28px;
}
}
</style>

View File

@ -1,13 +1,13 @@
<template>
<a-card class="mangement-container">
<div class="mangement-container">
<div class="left">
<a-input-search
<j-input-search
v-model:value="leftData.searchValue"
placeholder="请输入"
style="margin-bottom: 24px"
/>
<!-- 使用v-if用于解决异步加载数据后不展开的问题 -->
<a-tree
<j-tree
v-if="leftData.treeData.length > 0"
showLine
defaultExpandAll
@ -37,12 +37,12 @@
{{ dataRef.title }}
</span>
</template>
</a-tree>
</j-tree>
</div>
<div class="right">
<div class="btns">
<a-button type="primary" @click="table.clickSave"
>保存</a-button
<j-button type="primary" @click="table.clickSave"
>保存</j-button
>
</div>
<j-pro-table
@ -52,7 +52,7 @@
:dataSource="table.data"
>
<template #name="slotProps">
<a-input
<j-input
:disabled="slotProps.scale !== undefined"
v-model:value="slotProps.name"
placeholder="请输入名称"
@ -60,37 +60,37 @@
/>
</template>
<template #type="slotProps">
<a-input
<j-input
v-model:value="slotProps.type"
placeholder="请输入类型"
:maxlength="64"
/>
</template>
<template #length="slotProps">
<a-input-number
<j-input-number
v-model:value="slotProps.length"
:min="0"
:max="99999"
/>
</template>
<template #precision="slotProps">
<a-input-number
<j-input-number
v-model:value="slotProps.precision"
:min="0"
:max="99999"
/>
</template>
<template #notnull="slotProps">
<a-radio-group
<j-radio-group
v-model:value="slotProps.notnull"
button-style="solid"
>
<a-radio-button :value="true"></a-radio-button>
<a-radio-button :value="false"></a-radio-button>
</a-radio-group>
<j-radio-button :value="true"></j-radio-button>
<j-radio-button :value="false"></j-radio-button>
</j-radio-group>
</template>
<template #comment="slotProps">
<a-input
<j-input
v-model:value="slotProps.comment"
placeholder="请输入说明"
/>
@ -110,19 +110,19 @@
</PermissionButton>
</template>
</j-pro-table>
<a-botton class="add-row" @click="table.addRow">
<j-botton class="add-row" @click="table.addRow">
<AIcon type="PlusOutlined" /> 新增行
</a-botton>
</j-botton>
</div>
</a-card>
</div>
<div class="dialogs">
<a-modal
<j-modal
v-model:visible="dialog.visible"
title="新增"
@ok="dialog.handleOk"
>
<a-form :model="dialog.form" ref="addFormRef">
<a-form-item
<j-form :model="dialog.form" ref="addFormRef">
<j-form-item
label="名称"
name="name"
:rules="[
@ -148,13 +148,13 @@
},
]"
>
<a-input
<j-input
v-model:value="dialog.form.name"
placeholder="请输入名称"
/>
</a-form-item>
</a-form>
</a-modal>
</j-form-item>
</j-form>
</j-modal>
</div>
</template>
@ -311,8 +311,11 @@ const table = reactive({
name: leftData.selectedKeys[0],
columns: table.data,
};
saveTable_api(id, params).then(() => {
table.getTabelData(params.name);
saveTable_api(id, params).then((resp) => {
if (resp.status === 200) {
message.success('操作成功');
table.getTabelData(params.name);
}
});
},
clickDel: (row: any) => {},
@ -348,50 +351,48 @@ function init() {
<style lang="less" scoped>
.mangement-container {
margin: 24px;
padding: 24px;
background-color: transparent;
:deep(.ant-card-body) {
display: flex;
background-color: #fff;
background-color: #fff;
display: flex;
.left {
flex-basis: 280px;
padding-right: 24px;
box-sizing: border-box;
.left {
flex-basis: 280px;
padding-right: 24px;
box-sizing: border-box;
.ant-tree-treenode {
:deep(.ant-tree-treenode) {
width: 100%;
.ant-tree-switcher-noop {
display: none;
}
.ant-tree-node-content-wrapper {
width: 100%;
.ant-tree-switcher-noop {
display: none;
}
.ant-tree-node-content-wrapper {
.ant-tree-title {
width: 100%;
.ant-tree-title {
width: 100%;
}
}
&:first-child .ant-tree-node-selected {
background-color: transparent;
}
}
&:first-child .ant-tree-node-selected {
background-color: transparent;
}
}
.right {
width: calc(100% - 280px);
box-sizing: border-box;
border-left: 1px solid #f0f0f0;
}
.right {
width: calc(100% - 280px);
box-sizing: border-box;
border-left: 1px solid #f0f0f0;
.btns {
display: flex;
justify-content: right;
padding: 0px 24px;
}
.btns {
display: flex;
justify-content: right;
padding: 0px 24px;
}
.add-row {
display: block;
text-align: center;
width: 100%;
cursor: pointer;
}
.add-row {
display: block;
text-align: center;
width: 100%;
cursor: pointer;
}
}
}

View File

@ -1,18 +1,17 @@
<template>
<a-modal
<j-modal
class="edit-dialog-container"
:title="dialog.title"
width="1050px"
@ok="dialog.handleOk"
:confirmLoading="dialog.loading.value"
cancelText="取消"
okText="确定"
v-model:visible="dialog.visible.value"
visible
:title="dialogTitle"
:confirmLoading="loading"
@ok="confirm"
@cancel="emits('update:visible', false)"
>
<a-form ref="formRef" :model="form.data" layout="vertical">
<a-row :gutter="24">
<a-col :span="12">
<a-form-item
<j-form ref="formRef" :model="form.data" layout="vertical">
<j-row :gutter="24">
<j-col :span="12">
<j-form-item
name="name"
label="名称"
:rules="[
@ -20,72 +19,72 @@
{ max: 64, message: '最多可输入64个字符' },
]"
>
<a-input
<j-input
v-model:value="form.data.name"
placeholder="请输入名称"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
name="typeId"
label="类型"
:rules="[{ required: true, message: '请选择类型' }]"
>
<a-select
<j-select
v-model:value="form.data.typeId"
:options="form.typeOptions"
placeholder="请选择类型"
:disabled="!!form.data.id"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-if="form.data.typeId === 'rdb'">
<a-col :span="24">
<a-form-item
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-if="form.data.typeId === 'rdb'">
<j-col :span="24">
<j-form-item
:name="['shareConfig', 'url']"
label="URL"
:rules="[{ required: true, message: '请输入URL' }]"
>
<a-input
<j-input
v-model:value="form.data.shareConfig.url"
placeholder="请输入r2bdc或者jdbc连接地址示例r2dbc:mysql://127.0.0.1:3306/test"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
<a-col :span="24">
<a-form-item
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
<j-col :span="24">
<j-form-item
:name="['shareConfig', 'adminUrl']"
label="管理地址"
:rules="[{ required: true, message: '请输入管理地址' }]"
>
<a-input
<j-input
v-model:value="form.data.shareConfig.adminUrl"
placeholder="请输入管理地址示例http://localhost:15672"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
<a-col :span="24">
<a-form-item
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
<j-col :span="24">
<j-form-item
:name="['shareConfig', 'addresses']"
label="链接地址"
:rules="[{ required: true, message: '请输入链接地址' }]"
>
<a-input
<j-input
v-model:value="form.data.shareConfig.addresses"
placeholder="请输入链接地址示例localhost:5672"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-show="form.data.typeId">
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-show="form.data.typeId">
<j-col :span="12">
<j-form-item
:name="['shareConfig', 'username']"
label="用户名"
:rules="[
@ -96,14 +95,14 @@
},
]"
>
<a-input
<j-input
v-model:value="form.data.shareConfig.username"
placeholder="请输入用户名"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
:name="['shareConfig', 'password']"
label="密码"
:rules="[
@ -114,16 +113,16 @@
},
]"
>
<a-input-password
<j-input-password
v-model:value="form.data.shareConfig.password"
placeholder="请输入密码"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
<a-col :span="24">
<a-form-item
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
<j-col :span="24">
<j-form-item
:name="['shareConfig', 'virtualHost']"
label="虚拟域"
:rules="[
@ -134,16 +133,16 @@
},
]"
>
<a-input
<j-input
v-model:value="form.data.shareConfig.virtualHost"
placeholder="请输入虚拟域"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-if="form.data.typeId === 'rdb'">
<a-col :span="24">
<a-form-item
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-if="form.data.typeId === 'rdb'">
<j-col :span="24">
<j-form-item
:name="['shareConfig', 'schema']"
label="schema"
:rules="[
@ -154,28 +153,28 @@
},
]"
>
<a-input
<j-input
v-model:value="form.data.shareConfig.schema"
placeholder="请输入schema"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="24">
<a-form-item name="description" label="说明">
<a-textarea
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24">
<j-col :span="24">
<j-form-item name="description" label="说明">
<j-textarea
v-model:value="form.data.description"
placeholder="请输入说明"
:rows="3"
showCount
:maxlength="200"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
</j-form-item>
</j-col>
</j-row>
</j-form>
</j-modal>
</template>
<script setup lang="ts">
@ -186,38 +185,35 @@ import {
import { FormInstance, message } from 'ant-design-vue';
import type { dictItemType, optionItemType, sourceItemType } from '../typing';
const emits = defineEmits(['confirm']);
const emits = defineEmits(['confirm', 'update:visible']);
const props = defineProps<{
visible: boolean;
data: sourceItemType;
}>();
//
const dialog = {
title: '',
loading: ref<boolean>(false),
visible: ref<boolean>(false),
handleOk: () => {
formRef.value?.validate().then(() => {
form.submit();
});
},
//
openDialog: (row: sourceItemType) => {
if (row.id) dialog.title = '编辑数据源';
else dialog.title = '新增数据源';
form.data = { ...row };
nextTick(() => {
formRef.value?.clearValidate();
dialog.visible.value = true;
});
},
const dialogTitle = computed(() =>
props.data.id ? '编辑数据源' : '新增数据源',
);
const loading = ref(false);
const confirm = () => {
loading.value = true;
formRef.value
?.validate()
.then(() => form.submit())
.then((resp: any) => {
if (resp.status === 200) {
message.success('操作成功');
emits('confirm');
emits('update:visible', false);
}
})
.finally(() => (loading.value = false));
};
//
defineExpose({
openDialog: dialog.openDialog,
});
const formRef = ref<FormInstance>();
const form = reactive({
data: {
shareConfig: {},
...props.data,
} as sourceItemType,
typeOptions: [] as optionItemType[],
@ -232,14 +228,7 @@ const form = reactive({
});
},
submit: () => {
dialog.loading.value = true;
saveDataSource_api(form.data)
.then(() => {
message.success('操作成功');
emits('confirm');
dialog.visible.value = false;
})
.finally(() => (dialog.loading.value = false));
return saveDataSource_api(form.data);
},
});
form.getTypeOption();

View File

@ -1,14 +1,17 @@
<template>
<page-container>
<div class="data-source-container">
<Search :columns="query.columns" @search="query.search" />
<j-advanced-search
:columns="columns"
@search="(params:any)=>queryParams = {...params}"
/>
<j-pro-table
ref="tableRef"
:columns="table.columns"
:columns="columns"
:request="getDataSourceList_api"
model="TABLE"
:params="query.params.value"
:params="queryParams"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
}"
@ -41,7 +44,7 @@
}}
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<PermissionButton
:uhasPermission="`${permission}:update`"
type="link"
@ -120,13 +123,16 @@
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</a-space>
</j-space>
</template>
</j-pro-table>
<div class="dialogs">
<EditDialog ref="editDialogRef" @confirm="table.refresh" />
</div>
<EditDialog
v-if="dialog.visible"
v-model:visible="dialog.visible"
:data="dialog.selectItem"
@confirm="table.refresh"
/>
</div>
</page-container>
</template>
@ -150,113 +156,81 @@ const permission = 'system/DataSource';
const router = useRouter();
const query = {
columns: [
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
type: 'string',
},
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
type: 'string',
},
{
title: '类型',
dataIndex: 'typeId',
key: 'typeId',
search: {
type: 'select',
options: () =>
new Promise((resolve) => {
if (table.typeOptions.value.length > 0)
return resolve(table.typeOptions.value);
getDataTypeDict_api().then((resp: any) => {
const result = resp.result as dictItemType[];
resolve(
result.map((item) => ({
label: item.name,
value: item.id,
})),
);
});
}),
},
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
search: {
type: 'string',
},
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
ellipsis: true,
fixed: 'left',
search: {
type: 'select',
options: [
{
label: '正常',
value: 'enabled',
},
{
label: '已禁用',
value: 'disabled',
},
],
},
},
],
params: ref({}),
search: (params: object) => {
query.params.value = params;
width: '250px',
},
};
{
title: '类型',
dataIndex: 'typeId',
key: 'typeId',
search: {
type: 'select',
options: () =>
new Promise((resolve) => {
if (table.typeOptions.value.length > 0)
return resolve(table.typeOptions.value);
getDataTypeDict_api().then((resp: any) => {
const result = resp.result as dictItemType[];
resolve(
result.map((item) => ({
label: item.name,
value: item.id,
})),
);
});
}),
},
scopedSlots: true,
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
search: {
type: 'string',
},
ellipsis: true,
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
search: {
type: 'select',
options: [
{
label: '正常',
value: 'enabled',
},
{
label: '已禁用',
value: 'disabled',
},
],
},
scopedSlots: true,
width: '120px',
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
width: '200px',
fixed: 'right',
},
];
const queryParams = ref({});
const editDialogRef = ref(); //
const tableRef = ref<Record<string, any>>({}); //
const table = {
columns: [
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: '250px',
},
{
title: '类型',
dataIndex: 'typeId',
key: 'typeId',
scopedSlots: true,
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
ellipsis: true,
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
scopedSlots: true,
width: '120px',
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
width: '200px',
fixed: 'right',
},
],
typeOptions: ref<optionItemType[]>([]),
getTypeOption: () => {
@ -279,7 +253,8 @@ const table = {
},
//
openDialog: (row: sourceItemType | {}) => {
editDialogRef.value.openDialog({ shareConfig: {}, ...row });
dialog.selectItem = { shareConfig: {}, ...row };
dialog.visible = true;
},
//
clickDel: (row: sourceItemType) => {
@ -304,6 +279,11 @@ const table = {
},
};
table.getTypeOption();
const dialog = reactive({
visible: false,
selectItem: {} as any,
});
</script>
<style lang="less" scoped>

View File

@ -83,6 +83,7 @@ const selectItem = ref<any>({});
const dialogVisible = ref(false);
const dialogTitle = ref<'查看' | '新增' | '编辑'>('新增');
const openDialog = (mode: '查看' | '新增' | '编辑', row: object) => {
if(!routeParams.id) return message.warning('请先新增菜单基本信息')
selectItem.value = { ...row };
dialogTitle.value = mode;
dialogVisible.value = true;

View File

@ -1,132 +1,132 @@
<template>
<div class="setting-container">
<a-card>
<h5 class="top">
<exclamation-circle-outlined />
<span style="padding-left: 12px"
>基于系统源代码中的菜单数据配置系统菜单</span
>
</h5>
<h5 class="top">
<exclamation-circle-outlined />
<span style="padding-left: 12px"
>基于系统源代码中的菜单数据配置系统菜单</span
>
</h5>
<div class="transfer">
<!-- 左侧树 -->
<div class="basic-tree left">
<div class="title">
<div class="title-label">
<span>源菜单</span>
<a-tooltip>
<template #title
>根据系统代码自动读取的菜单数据</template
>
<question-circle-outlined />
</a-tooltip>
</div>
<div class="title-func">
<a-button
type="primary"
@click="dialogShow = true"
ghost
>一键拷贝</a-button
<div class="transfer">
<!-- 左侧树 -->
<div class="basic-tree left">
<div class="title">
<div class="title-label">
<span>源菜单</span>
<j-tooltip>
<template #title
>根据系统代码自动读取的菜单数据</template
>
</div>
<question-circle-outlined />
</j-tooltip>
</div>
<div class="content">
<a-input
v-model:value="transfer.data.leftSearchValue"
style="margin-bottom: 8px"
placeholder="请输入菜单名称"
<div class="title-func">
<j-button
type="primary"
@click="dialogShow = true"
ghost
>一键拷贝</j-button
>
<template #prefix>
<search-outlined style="color: #b3b3b3" />
</template>
</a-input>
<a-tree
autoExpandParent
:tree-data="transfer.data.leftTreeData"
draggable
>
<template #title="row">
<div>{{ row.name }}</div>
</template>
</a-tree>
</div>
</div>
<div class="center">
<a-button>请拖动至右侧</a-button>
</div>
<!-- 右侧树 -->
<div class="basic-tree right">
<div class="title">
<div class="title-label">
<span>系统菜单</span>
<a-tooltip>
<template #title
>菜单管理页面配置的菜单数据</template
>
<question-circle-outlined />
</a-tooltip>
</div>
</div>
<div class="content">
<a-input
v-model:value="transfer.data.rightSearchValue"
style="margin-bottom: 8px"
placeholder="请输入菜单名称"
>
<template #prefix>
<search-outlined style="color: #b3b3b3" />
</template>
</a-input>
<a-tree
draggable
autoExpandParent
:tree-data="transfer.data.rightTreeData"
@drop="transfer.onRightDrop"
>
<template #title="row">
<div
style="
display: flex;
justify-content: space-between;
"
>
<span>{{ row.name }}</span>
<a-popconfirm
title="确认删除?"
ok-text="确定"
cancel-text="取消"
@confirm="transfer.removeItem(row)"
>
<a-tooltip>
<template #title>删除</template>
<a-button
style="padding: 0"
type="link"
>
<close-outlined />
</a-button>
</a-tooltip>
</a-popconfirm>
</div>
</template>
</a-tree>
</div>
<div class="content">
<j-input
v-model:value="transfer.data.leftSearchValue"
style="margin-bottom: 8px"
placeholder="请输入菜单名称"
>
<template #prefix>
<search-outlined style="color: #b3b3b3" />
</template>
</j-input>
<j-tree
autoExpandParent
:tree-data="transfer.data.leftTreeData"
draggable
>
<template #title="row">
<div>{{ row.name }}</div>
</template>
</j-tree>
</div>
</div>
<div class="dialogs">
<a-modal
v-model:visible="dialogShow"
title="一键拷贝"
@ok="transfer.copy"
cancelText="取消"
okText="确认"
>
<p>源数据将会覆盖当前的系统菜单数据确定要一键拷贝吗</p>
</a-modal>
<div class="center">
<j-button>请拖动至右侧</j-button>
</div>
</a-card>
<!-- 右侧树 -->
<div class="basic-tree right">
<div class="title">
<div class="title-label">
<span>系统菜单</span>
<j-tooltip>
<template #title
>菜单管理页面配置的菜单数据</template
>
<question-circle-outlined />
</j-tooltip>
</div>
</div>
<div class="content">
<j-input
v-model:value="transfer.data.rightSearchValue"
style="margin-bottom: 8px"
placeholder="请输入菜单名称"
>
<template #prefix>
<search-outlined style="color: #b3b3b3" />
</template>
</j-input>
<j-tree
draggable
autoExpandParent
:tree-data="transfer.data.rightTreeData"
@drop="transfer.onRightDrop"
>
<template #title="row">
<div
style="
display: flex;
justify-content: space-between;
"
>
<span>{{ row.name }}</span>
<j-popconfirm
title="确认删除?"
ok-text="确定"
cancel-text="取消"
@confirm="transfer.removeItem(row)"
>
<j-tooltip>
<template #title>删除</template>
<j-button
style="padding: 0"
type="link"
>
<close-outlined />
</j-button>
</j-tooltip>
</j-popconfirm>
</div>
</template>
</j-tree>
</div>
</div>
</div>
<j-button type="primary" style="margin-top: 24px;">保存</j-button>
<div class="dialogs">
<j-modal
v-model:visible="dialogShow"
title="一键拷贝"
@ok="transfer.copy"
cancelText="取消"
okText="确认"
>
<p>源数据将会覆盖当前的系统菜单数据确定要一键拷贝吗</p>
</j-modal>
</div>
</div>
</template>
@ -282,6 +282,9 @@ const dialogShow = ref<boolean>(false);
<style lang="less" scoped>
.setting-container {
padding: 24px;
margin: 24px;
background-color: #fff;
.top {
font-size: inherit;
margin-bottom: 24px;

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
visible
:title="props.mode"
width="660px"
@ -8,8 +8,8 @@
:maskClosable="false"
:confirmLoading="loading"
>
<a-form :model="form.data" class="basic-form" ref="formRef">
<a-form-item
<j-form :model="form.data" class="basic-form" ref="formRef">
<j-form-item
label="编码"
name="id"
:rules="[
@ -17,12 +17,12 @@
{ max: 64, message: '最多可输入64个字符' },
]"
>
<a-input
<j-input
v-model:value="form.data.id"
:disabled="props.mode !== '新增'"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
label="名称"
name="name"
:rules="[
@ -30,12 +30,12 @@
{ max: 64, message: '最多可输入64个字符' },
]"
>
<a-input
<j-input
v-model:value="form.data.name"
:disabled="props.mode === '查看'"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
label="权限"
name="permissions"
:rules="[
@ -53,17 +53,17 @@
:disabled="props.mode === '查看'"
:key="form.data.id || ''"
/>
</a-form-item>
<a-form-item label="说明" name="describe">
<a-textarea
</j-form-item>
<j-form-item label="说明" name="describe">
<j-textarea
v-model:value="form.data.describe"
:rows="4"
placeholder="请输入说明"
:disabled="props.mode === '查看'"
/>
</a-form-item>
</a-form>
</a-modal>
</j-form-item>
</j-form>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -1,6 +1,6 @@
<template>
<div class="permission-choose-container">
<a-input
<j-input
v-model:value="searchValue"
style="width: 300px"
allowClear
@ -10,35 +10,35 @@
/>
<div class="permission-table">
<a-row :gutter="24" class="table-head">
<a-col :span="props.firstWidth">权限名称</a-col
><a-col :span="24 - props.firstWidth">权限操作</a-col>
</a-row>
<j-row :gutter="24" class="table-head">
<j-col :span="props.firstWidth">权限名称</j-col
><j-col :span="24 - props.firstWidth">权限操作</j-col>
</j-row>
<div class="table-body" :style="{ 'max-height': props.maxHeight }">
<a-row
<j-row
:gutter="24"
class="row"
v-for="rowItem in permission.list"
>
<a-col :span="props.firstWidth" class="item-name">
<a-checkbox
<j-col :span="props.firstWidth" class="item-name">
<j-checkbox
v-model:checked="rowItem.checkAll"
:indeterminate="rowItem.indeterminate"
@change="() => permission.selectAllOpions(rowItem)"
:disabled="props.disabled"
>
{{ rowItem.name }}
</a-checkbox>
</a-col>
<a-col :span="24 - props.firstWidth">
<a-checkbox-group
</j-checkbox>
</j-col>
<j-col :span="24 - props.firstWidth">
<j-checkbox-group
v-model:value="rowItem.checkedList"
:options="rowItem.options"
@change="((checkValue:string[])=>permission.selectOption(rowItem, checkValue))"
:disabled="props.disabled"
/>
</a-col>
</a-row>
</j-col>
</j-row>
</div>
</div>
</div>

View File

@ -1,13 +1,15 @@
<template>
<a-modal
v-model:visible="dialog.visible"
:title="dialog.title"
<j-modal
visible
:title="dialogTitle"
width="1000px"
@ok="dialog.handleOk"
@ok="confirm"
@cancel="emits('update:visible', false)"
:confirmLoading="loading"
class="edit-dialog-container"
>
<a-form ref="formRef" :model="form.data" layout="vertical">
<a-form-item
<j-form ref="formRef" :model="form.data" layout="vertical">
<j-form-item
name="id"
:rules="[
{ required: true, message: '请输入标识(ID)' },
@ -18,34 +20,37 @@
<template #label>
<span>标识</span>
<span class="required-icon">*</span>
<a-tooltip placement="top">
<j-tooltip placement="top">
<template #title>
<span>标识ID需与代码中的标识ID一致</span>
</template>
<question-circle-outlined style="color: #00000073" />
</a-tooltip>
<AIcon
type="QuestionCircleOutlined"
style="color: #00000073"
/>
</j-tooltip>
</template>
<a-input
<j-input
v-model:value="form.data.id"
placeholder="请输入标识(ID)"
:maxlength="64"
:disabled="dialog.title === '编辑'"
:disabled="dialogTitle === '编辑'"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
name="name"
label="名称"
:rules="[{ required: true, message: '请输入名称' }]"
>
<a-input
<j-input
v-model:value="form.data.name"
placeholder="请输入名称"
:maxlength="64"
/>
</a-form-item>
</a-form>
</j-form-item>
</j-form>
<a-table
<j-table
:columns="table.columns"
:data-source="actionTableData"
:pagination="false"
@ -61,54 +66,41 @@
<template
v-else-if="column.key !== 'index' && column.key !== 'act'"
>
<a-input v-model:value="record[column.key]" />
<j-input v-model:value="record[column.key]" />
</template>
<template v-else-if="column.key === 'act'">
<a-button
<j-button
class="delete-btn"
style="padding: 0"
type="link"
@click="table.clickRemove(index)"
>
<delete-outlined />
</a-button>
<AIcon type="DeleteOutlined" />
</j-button>
</template>
</template>
</a-table>
</j-table>
<div class="pager" v-show="pager.total > pager.pageSize">
<a-select v-model:value="pager.current" style="width: 60px">
<a-select-option v-for="(val, i) in pageArr" :value="i + 1">{{
<j-select v-model:value="pager.current" style="width: 60px">
<j-select-option v-for="(val, i) in pageArr" :value="i + 1">{{
i + 1
}}</a-select-option>
</a-select>
<a-pagination
}}</j-select-option>
</j-select>
<j-pagination
v-model:current="pager.current"
:page-size="pager.pageSize"
:total="pager.total"
/>
</div>
<a-button type="dashed" style="width: 100%" @click="table.clickAdd">
<plus-outlined /> 添加
</a-button>
<template #footer>
<a-button key="back" @click="dialog.visible = false">取消</a-button>
<a-button
key="submit"
type="primary"
:loading="form.loading"
@click="dialog.handleOk"
>确定</a-button
>
</template>
</a-modal>
<j-button type="dashed" style="width: 100%" @click="table.clickAdd">
<AIcon type="PlusOutlined" /> 添加
</j-button>
</j-modal>
</template>
<script setup lang="ts">
import { FormInstance, message } from 'ant-design-vue';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
import { Rule } from 'ant-design-vue/es/form';
import {
@ -122,42 +114,41 @@ const defaultAction = [
{ action: 'save', name: '保存', describe: '保存' },
{ action: 'delete', name: '删除', describe: '删除' },
];
const emits = defineEmits(['refresh']);
//
const dialog = reactive({
title: '',
visible: false,
handleOk: () => {
formRef.value?.validate().then(() => {
form.submit();
});
},
//
changeVisible: (status: boolean, defaultForm: any = {}) => {
dialog.title = defaultForm.id ? '编辑' : '新增';
form.data = { name: '', ...defaultForm };
table.data = defaultForm.id ? defaultForm.actions : [...defaultAction];
pager.total = table.data.length;
pager.current = 1;
dialog.visible = status;
nextTick(() => {
formRef.value?.clearValidate();
});
},
});
const emits = defineEmits(['refresh', 'update:visible']);
const props = defineProps<{
data: any;
visible: boolean;
}>();
const loading = ref(false);
const dialogTitle = computed(() => (props.data.id ? '编辑' : '新增'));
const confirm = () => {
loading.value = true;
formRef.value
?.validate()
.then(() => form.submit())
.then((resp) => {
if (resp.status === 200) {
message.success('操作成功');
emits('refresh');
emits('update:visible', false);
}
})
.finally(() => (loading.value = false));
};
//
const formRef = ref<FormInstance>();
const form = reactive({
loading: false,
data: {
name: '',
id: '',
...props.data,
},
rules: {
//
idCheck: (_rule: Rule, id: string, cb: Function) => {
if (!id) return cb('请输入标识(ID)');
if (dialog.title === '编辑') return cb();
if (props.data.id) return cb();
else if (!id) return cb('请输入标识(ID)');
checkId_api({ id })
.then((resp: any) => {
if (resp.status === 200 && !resp.result.passed)
@ -165,16 +156,6 @@ const form = reactive({
else cb();
})
.catch(() => cb('验证失败'));
// return new Promise((resolve) => {
// checkId_api({ id })
// .then((resp: any) => {
// if (resp.status === 200 && !resp.result.passed)
// resolve(resp.result.reason);
// else resolve('');
// })
// .catch(() => resolve(''));
// });
},
},
submit: () => {
@ -182,16 +163,9 @@ const form = reactive({
...form.data,
actions: table.data.filter((item: any) => item.action && item.name),
};
const api =
dialog.title === '编辑' ? editPermission_api : addPermission_api;
const api = props.data.id ? editPermission_api : addPermission_api;
api(params).then((resp) => {
if (resp.status === 200) {
message.error('操作成功');
emits('refresh');
dialog.visible = false;
}
});
return api(params);
},
});
@ -201,26 +175,26 @@ const table = reactive({
title: '-',
dataIndex: 'index',
key: 'index',
width:80,
align:'center'
width: 80,
align: 'center',
},
{
title: '操作类型',
dataIndex: 'action',
key: 'action',
width: 220
width: 220,
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: 220
width: 220,
},
{
title: '说明',
dataIndex: 'describe',
key: 'describe',
width: 220
width: 220,
},
{
title: '操作',
@ -228,7 +202,7 @@ const table = reactive({
key: 'act',
},
],
data: <any>[],
data: props.data.id ? props.data.actions : [...defaultAction],
clickRemove: (index: number) => {
pager.total -= 1;
table.data.splice(index, 1);
@ -251,7 +225,7 @@ const table = reactive({
const pager = reactive({
current: 1,
pageSize: 10,
total: 0,
total: table.data.length,
});
const pageArr = computed(() => {
const maxPageNum = Math.ceil(pager.total / pager.pageSize);
@ -267,11 +241,6 @@ const actionTableData = computed(() => {
return table.data.slice(startIndex, endIndex);
});
//
defineExpose({
openDialog: dialog.changeVisible,
});
</script>
<style lang="less" scoped>
@ -302,7 +271,7 @@ defineExpose({
}
.delete-btn {
color: #000000d9;
&:hover{
&:hover {
color: #415ed1;
}
}

View File

@ -1,29 +0,0 @@
<template>
<span class="status-label-container">
<i
class="circle"
:style="{ background: props.statusValue ? '#52c41a' : '#ff4d4f' }"
></i>
<span>{{ props.statusValue ? '启用' : '禁用' }}</span>
</span>
</template>
<script setup lang="ts">
const props = defineProps<{
statusValue: number;
}>();
</script>
<style lang="less" scoped>
.status-label-container {
display: flex;
align-items: center;
.circle {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 8px;
}
}
</style>

View File

@ -1,14 +1,17 @@
<template>
<page-container>
<div class="permission-container">
<Search :columns="query.columns" @search="query.search" />
<j-advanced-search
:columns="columns"
@search="(params:any) => (queryParams = params)"
/>
<j-pro-table
ref="tableRef"
:columns="table.columns"
:columns="columns"
:request="getPermission_api"
model="TABLE"
:params="query.params"
:params="queryParams"
:defaultParams="{ sorts: [{ name: 'id', order: 'asc' }] }"
>
<template #headerTitle>
@ -19,12 +22,12 @@
>
<AIcon type="PlusOutlined" />新增
</PermissionButton>
<a-dropdown trigger="hover">
<a-button>批量操作</a-button>
<j-dropdown trigger="hover">
<j-button>批量操作</j-button>
<template #overlay>
<a-menu>
<a-menu-item>
<a-upload
<j-menu>
<j-menu-item>
<j-upload
name="file"
action="#"
accept=".json"
@ -41,9 +44,9 @@
>
导入
</PermissionButton>
</a-upload>
</a-menu-item>
<a-menu-item>
</j-upload>
</j-menu-item>
<j-menu-item>
<PermissionButton
:uhasPermission="`${permission}:export`"
:popConfirm="{
@ -54,16 +57,23 @@
>
导出
</PermissionButton>
</a-menu-item>
</a-menu>
</j-menu-item>
</j-menu>
</template>
</a-dropdown>
</j-dropdown>
</template>
<template #status="slotProps">
<StatusLabel :status-value="slotProps.status" />
<BadgeStatus
:status="slotProps.status"
:text="slotProps.status ? '启用' : '禁用'"
:statusNames="{
1: 'success',
0: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<PermissionButton
:uhasPermission="`${permission}:update`"
type="link"
@ -113,13 +123,16 @@
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</a-space>
</j-space>
</template>
</j-pro-table>
<div class="dialogs">
<EditDialog ref="editDialogRef" @refresh="table.refresh" />
</div>
<EditDialog
v-if="dialog.visible"
v-model:visible="dialog.visible"
:data="dialog.selectItem"
@refresh="table.refresh"
/>
</div>
</page-container>
</template>
@ -127,7 +140,6 @@
<script setup lang="ts">
import PermissionButton from '@/components/PermissionButton/index.vue';
import EditDialog from './components/EditDialog.vue';
import StatusLabel from './components/StatusLabel.vue';
import { message } from 'ant-design-vue';
import {
getPermission_api,
@ -141,87 +153,63 @@ import { usePermissionStore } from '@/store/permission';
const permission = 'system/Permission';
const hasPermission = usePermissionStore().hasPermission;
const editDialogRef = ref(); //
const tableRef = ref<Record<string, any>>({}); //
//
const query = reactive({
columns: [
{
title: '标识',
dataIndex: 'id',
key: 'id',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
const columns = [
{
title: '标识',
dataIndex: 'id',
key: 'id',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
ellipsis: true,
search: {
rename: 'status',
type: 'select',
options: [
{
label: '启用',
value: 1,
},
{
label: '禁用',
value: 0,
},
],
},
},
],
params: {},
search: (params: object) => {
query.params = params;
},
});
{
title: '名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
ellipsis: true,
search: {
type: 'select',
options: [
{
label: '启用',
value: 1,
},
{
label: '禁用',
value: 0,
},
],
},
scopedSlots: true,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
width: '200px',
fixed: 'right',
scopedSlots: true,
},
];
const queryParams = ref({});
//
const table = reactive({
columns: [
{
title: '标识',
dataIndex: 'id',
key: 'id',
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
scopedSlots: true,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
},
],
tableData: [],
const tableRef = ref<Record<string, any>>({}); //
const table = {
//
openDialog: (row: object | undefined = {}) => {
editDialogRef.value.openDialog(true, row);
dialog.selectItem = { ...row };
dialog.visible = true;
},
//
clickImport: (file: File) => {
@ -248,7 +236,7 @@ const table = reactive({
clickExport: () => {
const params = {
paging: false,
...query.params,
...queryParams,
};
exportPermission_api(params).then((resp) => {
if (resp.status === 200) {
@ -283,12 +271,16 @@ const table = reactive({
refresh: () => {
tableRef.value.reload();
},
};
const dialog = reactive({
selectItem: {},
visible: false,
});
</script>
<style lang="less" scoped>
.permission-container {
.ant-dropdown-trigger {
margin-left: 12px;
}

View File

@ -1,14 +1,16 @@
<template>
<a-modal
v-model:visible="dialog.visible"
:title="dialog.title"
<j-modal
visible
:title="dialogTitle"
:maskClosable="false"
width="675px"
@ok="dialog.handleOk"
@ok="confirm"
@cancel="emits('update:visible', false)"
:confirmLoading="loading"
class="edit-dialog-container"
>
<a-form ref="formRef" :model="form.data" layout="vertical">
<a-form-item
<j-form ref="formRef" :model="form.data" layout="vertical">
<j-form-item
label="名称"
name="name"
:rules="[
@ -16,13 +18,13 @@
{ max: 64, message: '最多可输入64个字符' },
]"
>
<a-input
<j-input
v-model:value="form.data.name"
placeholder="请输入名称"
:maxlength="64"
/>
</a-form-item>
<a-form-item
</j-form-item>
<j-form-item
name="relation"
label="标识"
:rules="[
@ -31,76 +33,66 @@
{ validator: form.rules.checkRelation, trigger: 'change' },
]"
>
<a-input
<j-input
v-model:value="form.data.relation"
placeholder="请输入标识"
:maxlength="64"
:disabled="!!form.data.id"
/>
</a-form-item>
</j-form-item>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item
<j-row :gutter="24">
<j-col :span="12">
<j-form-item
name="objectType"
label="关联方"
:rules="[{ required: true, message: '请选择关联方' }]"
>
<a-select
<j-select
v-model:value="form.data.objectType"
:disabled="!!form.data.id"
>
<a-select-option
<j-select-option
v-for="item in form.objectList"
:value="item.id"
>{{ item.name }}</a-select-option
>{{ item.name }}</j-select-option
>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
</j-select>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
name="targetType"
label="标识"
label="被关联方"
:rules="[{ required: true, message: '请选择被关联方' }]"
>
<a-select
<j-select
v-model:value="form.data.targetType"
:disabled="!!form.data.id"
>
<a-select-option
v-for="item in form.targetList"
<j-select-option
v-for="item in targetList"
:value="item.id"
>{{ item.name }}</a-select-option
>{{ item.name }}</j-select-option
>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item
</j-select>
</j-form-item>
</j-col>
</j-row>
<j-form-item
name="description"
label="说明"
:rules="[{ max: 200, message: '最多可输入200个字符' }]"
>
<a-textarea
<j-textarea
v-model:value="form.data.description"
placeholder="请输入说明"
show-count
:maxlength="200"
/>
</a-form-item>
</a-form>
<template #footer>
<a-button key="back" @click="dialog.visible = false">取消</a-button>
<a-button
key="submit"
type="primary"
:loading="form.loading"
@click="dialog.handleOk"
>确定</a-button
>
</template>
</a-modal>
</j-form-item>
</j-form>
</j-modal>
</template>
<script setup lang="ts">
@ -112,53 +104,45 @@ import {
addRelation_api,
editRelation_api,
} from '@/api/system/relationship';
import { dictItemType } from '../../DataSource/typing';
const emits = defineEmits(['refresh']);
const emits = defineEmits(['refresh', 'update:visible']);
const props = defineProps<{
visible: boolean;
data: formType;
}>();
//
const dialog = reactive({
title: '',
visible: false,
handleOk: () => {
formRef.value?.validate().then(() => {
form.submit();
});
},
//
changeVisible: (status: boolean, defaultForm: formType) => {
dialog.title = defaultForm.id ? '编辑' : '新增';
form.data = { ...defaultForm };
dialog.visible = status;
nextTick(() => {
formRef.value?.clearValidate();
});
},
});
//
const initForm: formType = {
name: '',
relation: '',
objectType: '',
targetType: '',
description: '',
const loading = ref(false);
const dialogTitle = computed(() => (props.data.id ? '编辑' : '新增'));
const confirm = () => {
loading.value = true;
formRef.value
?.validate()
.then(() => form.submit())
.then((resp: any) => {
if (resp.status === 200) {
message.success('操作成功');
emits('refresh');
emits('update:visible', false);
}
})
.finally(() => (loading.value = false));
};
const formRef = ref<FormInstance>();
const form = reactive({
loading: false,
data: {} as formType,
data: props.data,
rules: {
checkRelation: (_rule: Rule, value: string): any => {
if (!value) return '';
const reg = new RegExp('^[0-9a-zA-Z_\\\\-]+$');
if (reg.test(value)) return Promise.resolve();
else
return Promise.reject(
'标识只能由数字、字母、下划线、中划线组成',
);
return reg.test(value)
? Promise.resolve()
: Promise.reject('标识只能由数字、字母、下划线、中划线组成');
},
},
objectList: [] as any[],
targetList: [] as any[],
getObjectList: () => {
getObjectList_api().then((resp: any) => {
@ -166,43 +150,23 @@ const form = reactive({
});
},
submit: () => {
formRef.value?.validate().then(() => {
const params = {
...form.data,
objectTypeName: form.objectList.find(
(item) => item.id === form.data.objectType,
).name,
targetTypeName: form.targetList.find(
(item) => item.id === form.data.targetType,
).name,
};
const api =
dialog.title === '编辑'
? editRelation_api
: addRelation_api;
api(params).then((resp: any) => {
if (resp.status === 200) {
message.success('操作成功');
emits('refresh');
dialog.visible = false;
}
});
});
const params = {
...form.data,
objectTypeName: form.objectList.find(
(item) => item.id === form.data.objectType,
).name,
targetTypeName: targetList.value.find(
(item: dictItemType) => item.id === form.data.targetType,
)?.name,
};
const api = props.data.id ? editRelation_api : addRelation_api;
return api(params);
},
});
form.getObjectList();
watch(
() => form.data.objectType,
(n) => {
form.targetList = n === 'device' ? [{ id: 'user', name: '用户' }] : [];
},
const targetList = computed(() =>
form.data.objectType === 'device' ? [{ id: 'user', name: '用户' }] : [],
);
//
defineExpose({
openDialog: dialog.changeVisible,
});
form.getObjectList();
type formType = {
name: string;
@ -213,5 +177,3 @@ type formType = {
id?: string;
};
</script>
<style scoped></style>

View File

@ -1,14 +1,17 @@
<template>
<page-container>
<div class="relationship-container">
<Search :columns="query.columns" @search="query.search" />
<j-advanced-search
:columns="columns"
@search="(params:any)=>queryParams = {...params}"
/>
<j-pro-table
ref="tableRef"
:columns="table.columns"
:columns="columns"
:request="getRelationshipList_api"
model="TABLE"
:params="query.params.value"
:params="queryParams"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
}"
@ -23,7 +26,7 @@
</PermissionButton>
</template>
<template #action="slotProps">
<a-space :size="16">
<j-space :size="16">
<PermissionButton
:uhasPermission="`${permission}:update`"
type="link"
@ -47,11 +50,16 @@
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</a-space>
</j-space>
</template>
</j-pro-table>
<EditDialog ref="editDialogRef" @refresh="table.refresh" />
<EditDialog
v-if="dialog.visible"
v-model:visible="dialog.visible"
:data="dialog.selectRow"
@refresh="table.refresh"
/>
</div>
</page-container>
</template>
@ -67,105 +75,79 @@ import EditDialog from './components/EditDialog.vue';
const permission = 'system/Relationship';
const query = {
columns: [
{
title: '名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
{
title: '关联方',
dataIndex: 'objectTypeName',
key: 'objectTypeName',
ellipsis: true,
fixed: 'left',
search: {
type: 'select',
options: [
{
label: '启用',
value: 1,
},
{
label: '禁用',
value: 0,
},
],
},
},
{
title: '被关联方',
dataIndex: 'targetType',
key: 'targetType',
ellipsis: true,
fixed: 'left',
search: {
type: 'select',
options: [
{
label: '用户',
value: 'user',
},
],
},
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
},
],
params: ref({}),
search: (params: object) => {
query.params.value = params;
},
};
{
title: '关联方',
dataIndex: 'objectTypeName',
key: 'objectTypeName',
ellipsis: true,
fixed: 'left',
search: {
type: 'select',
options: [
{
label: '用户',
value: '用户',
},
{
label: '设备',
value: '设备',
},
],
},
},
{
title: '被关联方',
dataIndex: 'targetTypeName',
key: 'targetTypeName',
ellipsis: true,
fixed: 'left',
search: {
rename: 'targetType',
type: 'select',
options: [
{
label: '用户',
value: 'user',
},
],
},
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
},
];
const queryParams = ref({});
const editDialogRef = ref(); //
const tableRef = ref<Record<string, any>>({}); //
const table = {
columns: [
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '关联方',
dataIndex: 'objectTypeName',
key: 'objectTypeName',
},
{
title: '被关联方',
dataIndex: 'targetTypeName',
key: 'targetTypeName',
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
},
],
//
openDialog: (row: object | undefined = {}) => {
editDialogRef.value.openDialog(true, row);
dialog.selectRow = { ...row };
dialog.visible = true;
},
//
clickDel: (row: any) => {
@ -181,6 +163,11 @@ const table = {
tableRef.value.reload();
},
};
const dialog = reactive({
selectRow: {} as any,
visible: false,
});
</script>
<style lang="less" scoped>

View File

@ -10,6 +10,7 @@ import VueSetupExtend from 'vite-plugin-vue-setup-extend'
import { createStyleImportPlugin, AndDesignVueResolve } from 'vite-plugin-style-import'
import * as path from 'path'
import monacoEditorPlugin from 'vite-plugin-monaco-editor';
// import { JetlinksVueResolver } from 'jetlinks-ui-components/lib/plugin/resolve'
import { JetlinksVueResolver } from './plugin/jetlinks'
import copy from 'rollup-plugin-copy';