feat: 添加设备详情页面权限控制和CRON表达式配置功能
- 在设备详情页新增权限访问控制,防止无权限用户访问产品详情 - 引入并集成 shiyzhangcron 组件库实现 CRON 表达式可视化配置 - 新增 CronPickerModal 弹窗组件用于选择和确认 CRON 表达式 - 更新 package.json 添加 shiyzhangcron 依赖 - 调整按钮权限控制逻辑以适配新的权限校验方式 - 优化部分页面样式与交互逻辑
This commit is contained in:
parent
df397ba648
commit
8727d91415
|
@ -53,6 +53,7 @@
|
|||
"monaco-editor": "^0.52.2",
|
||||
"pinia": "catalog:",
|
||||
"rxjs": "^7.8.2",
|
||||
"shiyzhangcron": "^0.1.5",
|
||||
"tinymce": "^7.3.0",
|
||||
"unplugin-vue-components": "^0.27.3",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { EasyCronInner } from 'shiyzhangcron';
|
||||
|
||||
import 'shiyzhangcron/dist/style.css';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
value: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits(['update:visible', 'confirm', 'cancel']);
|
||||
|
||||
const innerValue = ref(props.value);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(v) => {
|
||||
innerValue.value = v;
|
||||
},
|
||||
);
|
||||
|
||||
const isVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value),
|
||||
});
|
||||
|
||||
const onCancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
const onOk = () => {
|
||||
emit('confirm', innerValue.value);
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
const onCronChange = (v: string) => {
|
||||
innerValue.value = v;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:open="isVisible"
|
||||
:title="title || 'Cron 配置'"
|
||||
width="860px"
|
||||
:mask-closable="false"
|
||||
@ok="onOk"
|
||||
@cancel="onCancel"
|
||||
>
|
||||
<EasyCronInner
|
||||
v-model:value="innerValue"
|
||||
input-area="true"
|
||||
@change="onCronChange"
|
||||
/>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<div>当前选择: {{ innerValue }}</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
|
@ -1,10 +1,57 @@
|
|||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
|
||||
import 'shiyzhangcron/dist/style.css';
|
||||
|
||||
const CronPickerModal = defineAsyncComponent(
|
||||
() => import('../../../components/CronPickerModal/index.vue'),
|
||||
);
|
||||
|
||||
const easyCronInnerValue = ref('* * * * * ? *');
|
||||
|
||||
const cronModalVisible = ref(false);
|
||||
|
||||
const openCronModal = () => {
|
||||
cronModalVisible.value = true;
|
||||
};
|
||||
|
||||
const onCronConfirm = (val: string) => {
|
||||
easyCronInnerValue.value = val;
|
||||
cronModalVisible.value = false;
|
||||
};
|
||||
|
||||
const onCronCancel = () => {
|
||||
cronModalVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<h1 class="text-2xl font-bold mb-4">Test Detail Page</h1>
|
||||
<p>This is a placeholder for the test detail page.</p>
|
||||
<h1 class="mb-4 text-2xl font-bold">Test Detail Page</h1>
|
||||
|
||||
<div class="flex items-center gap-2" style="max-width: 640px">
|
||||
<input
|
||||
:value="easyCronInnerValue"
|
||||
type="text"
|
||||
placeholder="请输入 CRON 表达式"
|
||||
class="flex-1 rounded border border-gray-300 px-3 py-1"
|
||||
/>
|
||||
<button
|
||||
class="rounded bg-blue-600 px-3 py-1 text-white"
|
||||
@click="openCronModal"
|
||||
>
|
||||
配置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="mt-4">当前cron值为: {{ easyCronInnerValue }}</p>
|
||||
|
||||
<CronPickerModal
|
||||
v-if="cronModalVisible"
|
||||
:visible="cronModalVisible"
|
||||
:value="easyCronInnerValue"
|
||||
@confirm="onCronConfirm"
|
||||
@cancel="onCronCancel"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { computed, defineAsyncComponent, onMounted, onUnmounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons-vue';
|
||||
|
@ -12,6 +13,8 @@ import { deviceStateOptions } from '#/constants/dicts';
|
|||
import { useDeviceStore } from '#/store/device';
|
||||
import { getWebSocket } from '#/utils/websocket';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
const BasicInfo = defineAsyncComponent(
|
||||
() => import('./components/BasicInfo.vue'),
|
||||
);
|
||||
|
@ -106,6 +109,10 @@ const handleTabChange = (key: string) => {
|
|||
};
|
||||
|
||||
const handleProductClick = () => {
|
||||
if (!hasAccessByCodes(['device:product:query'])) {
|
||||
message.warning('暂无权限');
|
||||
return;
|
||||
}
|
||||
router.push(`/device/product/detail/${currentDevice.value.productId}`);
|
||||
};
|
||||
|
||||
|
|
|
@ -349,10 +349,7 @@ const [DeviceDrawer, drawerApi] = useVbenDrawer({
|
|||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Space>
|
||||
<ghost-button
|
||||
v-access:code="['device:device:view']"
|
||||
@click.stop="handleView(record)"
|
||||
>
|
||||
<ghost-button @click.stop="handleView(record)">
|
||||
详情
|
||||
</ghost-button>
|
||||
<ghost-button
|
||||
|
|
|
@ -211,7 +211,7 @@ onMounted(() => {
|
|||
<div v-if="!accessInfo.id" class="empty-state">
|
||||
<Empty>
|
||||
<template #description>
|
||||
<span v-if="hasAccessByCodes('device:product:edit')">
|
||||
<span v-if="hasAccessByCodes(['device:product:edit'])">
|
||||
请先
|
||||
<a-button type="link" @click="handleSelectAccess">选择</a-button>
|
||||
设备接入网关,用以提供设备接入能力
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
// types/shiyzhangcron.d.ts
|
||||
declare module 'shiyzhangcron';
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><rect width="20" height="20" fill="none"/><g fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"><path d="M18 6.923H2v11h16zm-16-1a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-11a1 1 0 0 0-1-1z"/><path d="M6 9.423a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5m-1 3a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m1 3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5m5.768-13.025a2.5 2.5 0 0 0-3.536 0L4.354 6.277a.5.5 0 1 1-.708-.708l3.88-3.878a3.5 3.5 0 0 1 4.949 0l3.879 3.879a.5.5 0 1 1-.708.707z"/></g></svg>
|
After Width: | Height: | Size: 626 B |
|
@ -63,6 +63,9 @@ const SvgMaterialSymbolsDashboardOutlineIcon = createIconifyIcon(
|
|||
const SvgAntDesignDashboardOutlinedIcon = createIconifyIcon(
|
||||
'svg:ant-design--dashboard-outlined',
|
||||
);
|
||||
const SvgPepiconsPencilBulletinNoticeIcon = createIconifyIcon(
|
||||
'svg:pepicons-pencil--bulletin-notice',
|
||||
);
|
||||
|
||||
export {
|
||||
SvgAntDesignDashboardOutlinedIcon,
|
||||
|
@ -87,6 +90,7 @@ export {
|
|||
SvgMaterialSymbolsDashboardOutlineIcon,
|
||||
SvgMaxKeyIcon,
|
||||
SvgMdiAccountOnlineOutlineIcon,
|
||||
SvgPepiconsPencilBulletinNoticeIcon,
|
||||
SvgProiconsDocumentIcon,
|
||||
SvgQQIcon,
|
||||
SvgRiAlarmWarningLineIcon,
|
||||
|
|
Loading…
Reference in New Issue