1.切换租户-切换项目

2.添加 设备模板
This commit is contained in:
WangChenChen
2023-10-25 22:31:36 +08:00
parent 3eb0d1c60e
commit 5edc33f5ec
21 changed files with 451 additions and 98 deletions

View File

@@ -5,7 +5,7 @@ x-tenant-id: {{tenantId}}
Authorization: Bearer {{tokenValue}}
{
"tenantId": "baidu",
"tenantId": "baidu120",
"logo": "https://baidu.com/baidu.png",
"name": "百度科技有限公司",
"email": "baidu@qq.com",

View File

@@ -16,7 +16,7 @@ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Fi
"username": "admin",
"password": "123456",
"codeKey": "5jXzuwcoUzbtnHNh",
"codeText": "re6e"
"codeText": "jap5"
}
> {%

View File

@@ -66,7 +66,7 @@ public class ApiCmsProjectServiceImpl implements ApiCmsProjectService {
delegate(po.getTenantId(), () -> {
var project = new CmsProject()
.setProjectId(10001L)
.setProjectName(po.getName())
.setProjectName("默认项目")
.setLinkman(po.getLinkman())
.setContactNumber(po.getContactNumber())
.setAreaCode(po.getAreaCode())

View File

@@ -24,6 +24,10 @@ import org.springframework.stereotype.Service;
@AllArgsConstructor
public class CmsDeviceServiceImpl extends BaseServiceImpl<CmsDeviceMapper, CmsDevice> implements CmsDeviceService {
@Override
public boolean save(CmsDevice entity) {
entity.setDeviceId(null);
return super.save(entity);
}
}

View File

@@ -1,8 +1,6 @@
package com.xaaef.molly.corems.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -12,7 +10,6 @@ import com.xaaef.molly.corems.mapper.CmsProjectMapper;
import com.xaaef.molly.corems.service.CmsProjectService;
import com.xaaef.molly.corems.vo.ResetPasswordVO;
import com.xaaef.molly.internal.api.ApiPmsDeptService;
import com.xaaef.molly.internal.dto.PmsDeptDTO;
import com.xaaef.molly.tenant.base.service.impl.BaseServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -20,7 +17,6 @@ import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.xaaef.molly.auth.jwt.JwtSecurityUtils.*;

View File

@@ -8,6 +8,7 @@ import liquibase.resource.ClassLoaderResourceAccessor;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
@@ -35,6 +36,8 @@ public class SchemaDataSourceManager implements DatabaseManager {
// 默认租户的数据源
private final DataSource dataSource;
private final JdbcTemplate jdbcTemplate;
private final MultiTenantProperties multiTenantProperties;
private final DataSourceProperties dataSourceProperties;
@@ -65,25 +68,24 @@ public class SchemaDataSourceManager implements DatabaseManager {
@Override
public void updateTable(String tenantId) {
log.info("tenantId: {} update table ...", tenantId);
// 判断数据库是否存在!不存在就创建
var tenantDbName = multiTenantProperties.getPrefix() + tenantId;
var sql = String.format("CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;", tenantDbName);
jdbcTemplate.execute(sql);
try {
// 判断 schema 是否存在。不存在就创建
var conn = dataSource.getConnection();
// 判断数据库是否存在!不存在就创建
String tenantDbName = multiTenantProperties.getPrefix() + tenantId;
String sql = String.format("CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;", tenantDbName);
conn.createStatement().execute(sql);
// 创建一次性的 jdbc 链接。只是用来生成表结构的。用完就关闭。
var conn1 = new JdbcConnection(getTempConnection(tenantDbName));
var tempConn = getTempConnection(tenantDbName);
log.info("getTempConnection: {} ", tempConn);
var changeLogPath = multiTenantProperties.getOtherChangeLog();
// 使用 Liquibase 创建表结构
if (multiTenantProperties.getOtherChangeLog().startsWith(CLASSPATH_URL_PREFIX)) {
changeLogPath = multiTenantProperties.getOtherChangeLog().replaceFirst(CLASSPATH_URL_PREFIX, "");
}
var liquibase = new Liquibase(changeLogPath, new ClassLoaderResourceAccessor(), conn1);
var liquibase = new Liquibase(changeLogPath, new ClassLoaderResourceAccessor(), new JdbcConnection(tempConn));
// 更新 数据库 结构体
liquibase.update();
// 关闭链接
conn1.close();
tempConn.close();
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
@@ -96,14 +98,7 @@ public class SchemaDataSourceManager implements DatabaseManager {
log.warn("tenantId: {} delete table ...", tenantId);
String tenantDbName = multiTenantProperties.getPrefix() + tenantId;
String sql = String.format("DROP DATABASE %s ;", tenantDbName);
try {
var conn = getTempConnection(tenantDbName);
conn.createStatement().execute(sql);
conn.close();
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
}
jdbcTemplate.execute(sql);
}
@@ -118,6 +113,7 @@ public class SchemaDataSourceManager implements DatabaseManager {
var oldDbName = getOldDbName(dataSourceProperties.getUrl());
// 替换连接池中的数据库名称
var dataSourceUrl = dataSourceProperties.getUrl().replaceFirst(oldDbName, tenantDbName);
//3.获取数据库连接对象
return DriverManager.getConnection(dataSourceUrl,
dataSourceProperties.getUsername(),

33
web-ui/src/api/device.ts Normal file
View File

@@ -0,0 +1,33 @@
import { IJsonResult, IPageResult, ISearchQuery } from "@/types/base"
import { ICmsDevice } from "@/types/cms"
import { httpDelete, httpGet, httpPost, httpPut } from "@/utils/service"
/** 根据Id查询 */
export const getDeviceApi = (id: number) => {
return httpGet<number, IJsonResult<ICmsDevice>>(`/cms/device/${id}`)
}
/** 分页查询所有 */
export const queryDeviceApi = (data: ISearchQuery) => {
return httpGet<ISearchQuery, IPageResult<ICmsDevice>>("/cms/device/query", data)
}
/** 查询所有 */
export const listDeviceApi = () => {
return httpGet<any, IJsonResult<ICmsDevice[]>>("/cms/device/list")
}
/** 新增 */
export const saveDeviceApi = (data: ICmsDevice) => {
return httpPost<ICmsDevice, IJsonResult<ICmsDevice>>("/cms/device", data)
}
/** 修改 */
export const updateDeviceApi = (data: ICmsDevice) => {
return httpPut<any, IJsonResult<boolean>>("/cms/device", data)
}
/** 删除 */
export const deleteDeviceApi = (id: number) => {
return httpDelete<number, IJsonResult<boolean>>(`/cms/device/${id}`)
}

View File

@@ -13,7 +13,14 @@
<el-main>
<el-table :data="simpleProjects" style="width: 100%">
<el-table-column prop="projectId" label="项目ID" />
<el-table-column prop="projectName" label="项目名称" />
<el-table-column prop="projectName" label="项目名称">
<template #default="scope">
<strong style="color: #409eff" v-if="!currentProjectId(scope.row.projectId)">
{{ scope.row.projectName }}
</strong>
<span v-else> {{ scope.row.projectName }} </span>
</template>
</el-table-column>
<el-table-column prop="linkman" label="联系人" />
<el-table-column prop="areaCode" label="行政区域">
<template #default="scope">
@@ -50,7 +57,7 @@
<script setup lang="ts">
import { computed, reactive, ref } from "vue"
import { Search, UserFilled } from "@element-plus/icons-vue"
import { Search } from "@element-plus/icons-vue"
import { ISearchQuery, ISimpleProject } from "@/types/base"
import { simpleQueryProjectApi } from "@/api/project"
import { useProjectStoreHook } from "@/store/modules/project"
@@ -76,13 +83,15 @@ const loading = ref(false)
const params = reactive({
pageTotal: 0,
pageIndex: 1,
pageSize: 10,
pageSize: 5,
keywords: ""
})
const dialogVisible = ref(false)
const emit = defineEmits<{
(e: "handleSwitch", id: string): void
}>()
// 切换租户
// 切换项目
const handleSwitchClick = (t: ISimpleProject) => {
const project: ISimpleProject = {
projectId: t.projectId,
@@ -92,7 +101,7 @@ const handleSwitchClick = (t: ISimpleProject) => {
linkman: t.linkman
}
projectStore.setCurrentProject(project)
dialogVisible.value = false
emit("handleSwitch", t.projectName)
}
const handleSizeChange = (val: number) => {
@@ -131,9 +140,11 @@ const searchProjectList = () => {
})
}
const searchList = () => searchProjectList()
// 输出组件的方法,让外部组件可以调用
defineExpose({
searchProjectList
searchList
})
</script>
@@ -141,9 +152,11 @@ defineExpose({
.el-header {
--el-header-padding: 0px;
}
.el-main {
--el-main-padding: 20px 0px;
}
.el-footer {
--el-main-padding: 0px;
}

View File

@@ -55,13 +55,15 @@
</template>
<script setup lang="ts">
import { computed, reactive, ref } from "vue"
import { computed, reactive, ref, onMounted } from "vue"
import { Search, UserFilled } from "@element-plus/icons-vue"
import { ISearchQuery, ISimpleTenant } from "@/types/base"
import { simpleQueryTenantApi } from "@/api/tenant"
import { useTenantStoreHook } from "@/store/modules/tenant"
import { useProjectStoreHook } from "@/store/modules/project"
const tenantStore = useTenantStoreHook()
const projectStore = useProjectStoreHook()
const simpleTenants = ref<ISimpleTenant[]>()
@@ -75,7 +77,9 @@ const params = reactive({
keywords: ""
})
const dialogVisible = ref(false)
const emit = defineEmits<{
(e: "handleSwitch", id: string): void
}>()
// 切换租户
const handleSwitchClick = (t: ISimpleTenant) => {
@@ -86,7 +90,9 @@ const handleSwitchClick = (t: ISimpleTenant) => {
linkman: t.linkman
}
tenantStore.setCurrentTenant(tenant)
dialogVisible.value = false
// 重置当前项目为默认项目
projectStore.resetCurrentProject()
emit("handleSwitch", t.name)
}
const handleSizeChange = (val: number) => {
@@ -124,9 +130,16 @@ const searchTenantList = () => {
loading.value = false
})
}
onMounted(() => {
searchTenantList()
})
const searchList = () => searchTenantList()
// 输出组件的方法,让外部组件可以调用
defineExpose({
searchTenantList
searchList
})
</script>

View File

@@ -0,0 +1,17 @@
import { computed } from "vue"
import { useProjectStoreHook } from "@/store/modules/project"
import { useTenantStoreHook } from "@/store/modules/tenant"
const projectStore = useProjectStoreHook()
const tenantStore = useTenantStoreHook()
// 租户ID 和 项目 组成的 唯一ID
const currentOnlyId = computed(() => {
return tenantStore.getCurrentTenantId() + projectStore.getCurrentProjectId()
})
export function useTenantAndProject() {
return {
currentOnlyId
}
}

View File

@@ -3,13 +3,12 @@ import { computed } from "vue"
import { storeToRefs } from "pinia"
import { useAppStore } from "@/store/modules/app"
import { useSettingsStore } from "@/store/modules/settings"
import { useTenantStoreHook } from "@/store/modules/tenant"
import { useTenantAndProject } from "@/hooks/useTenantAndProject"
import { AppMain, NavigationBar, Sidebar, TagsView } from "./components"
import { DeviceEnum } from "@/constants/app-key"
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const tenantStore = useTenantStoreHook()
const { showTagsView, fixedHeader } = storeToRefs(settingsStore)
@@ -28,9 +27,7 @@ const handleClickOutside = () => {
appStore.closeSidebar(false)
}
const currentTenantId = computed(() => {
return tenantStore.getCurrentTenantId()
})
const { currentOnlyId } = useTenantAndProject()
</script>
<template>
@@ -47,7 +44,7 @@ const currentTenantId = computed(() => {
<TagsView v-show="showTagsView" />
</div>
<!-- 页面主体内容 -->
<AppMain class="app-main" :key="currentTenantId" />
<AppMain class="app-main" :key="currentOnlyId" />
</div>
</div>
</template>

View File

@@ -2,13 +2,12 @@
import { computed } from "vue"
import { storeToRefs } from "pinia"
import { useAppStore } from "@/store/modules/app"
import { useTenantStoreHook } from "@/store/modules/tenant"
import { useTenantAndProject } from "@/hooks/useTenantAndProject"
import { useSettingsStore } from "@/store/modules/settings"
import { AppMain, NavigationBar, Sidebar, TagsView, Logo } from "./components"
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const tenantStore = useTenantStoreHook()
const { showTagsView, showLogo } = storeToRefs(settingsStore)
@@ -19,9 +18,7 @@ const layoutClasses = computed(() => {
}
})
const currentTenantId = computed(() => {
return tenantStore.getCurrentTenantId()
})
const { currentOnlyId } = useTenantAndProject()
</script>
<template>
@@ -39,7 +36,7 @@ const currentTenantId = computed(() => {
<!-- 左侧边栏 -->
<Sidebar class="sidebar-container" />
<!-- 页面主体内容 -->
<AppMain class="app-main" :key="currentTenantId" />
<AppMain class="app-main" :key="currentOnlyId" />
</div>
</div>
</template>

View File

@@ -1,18 +1,12 @@
<script lang="ts" setup>
import { computed } from "vue"
import { storeToRefs } from "pinia"
import { useSettingsStore } from "@/store/modules/settings"
import { useTenantStoreHook } from "@/store/modules/tenant"
import { useTenantAndProject } from "@/hooks/useTenantAndProject"
import { AppMain, NavigationBar, TagsView, Logo } from "./components"
const settingsStore = useSettingsStore()
const tenantStore = useTenantStoreHook()
const { showTagsView, showLogo } = storeToRefs(settingsStore)
const currentTenantId = computed(() => {
return tenantStore.getCurrentTenantId()
})
const { currentOnlyId } = useTenantAndProject()
</script>
<template>
@@ -28,7 +22,7 @@ const currentTenantId = computed(() => {
<!-- 主容器 -->
<div :class="{ hasTagsView: showTagsView }" class="main-container">
<!-- 页面主体内容 -->
<AppMain class="app-main" :key="currentTenantId" />
<AppMain class="app-main" :key="currentOnlyId" />
</div>
</div>
</template>

View File

@@ -9,12 +9,12 @@
class="search-modal__private"
append-to-body
>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="tabsHandleClick">
<el-tabs v-model="activeName" type="card" @tab-click="tabsHandleClick">
<el-tab-pane label="切换租户" name="tenant">
<SearchTenant ref="childTenant" />
<SearchTenant @handle-switch="childHandleSwitch" ref="childTenant" />
</el-tab-pane>
<el-tab-pane label="切换项目" name="project">
<SearchProject ref="childProject" />
<SearchProject @handle-switch="childHandleSwitch" ref="childProject" />
</el-tab-pane>
</el-tabs>
</el-dialog>
@@ -22,13 +22,13 @@
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from "vue"
import { computed, ref } from "vue"
import { Search } from "@element-plus/icons-vue"
import type { TabsPaneContext } from "element-plus"
import { useAppStore } from "@/store/modules/app"
import { useTenantStoreHook } from "@/store/modules/tenant"
import { useProjectStoreHook } from "@/store/modules/project"
import { DeviceEnum } from "@/constants/app-key"
import type { TabsPaneContext } from "element-plus"
import SearchTenant from "@/components/SearchTenant/index.vue"
import SearchProject from "@/components/SearchProject/index.vue"
@@ -43,7 +43,10 @@ const modalWidth = computed(() => (appStore.device === DeviceEnum.Mobile ? "80vw
const dialogVisible = ref(false)
const activeName = ref("tenant")
// 租户切换组件
const childTenant = ref()
// 项目切换组件
const childProject = ref()
const value = computed(() => `${tenantStore.getCurrentTenant().name} / ${projectStore.getCurrentProject().projectName}`)
@@ -58,15 +61,16 @@ const handleClose = (done: () => void) => {
done()
}
onMounted(() => {
childTenant.value.searchTenantList()
})
const childHandleSwitch = (str: string) => {
console.log("str :>> ", str)
dialogVisible.value = false
}
const tabsHandleClick = (tab: TabsPaneContext) => {
if (tab.paneName === "tenant") {
childTenant.value.searchTenantList()
childTenant.value.searchList()
} else {
childProject.value.searchProjectList()
childProject.value.searchList()
}
}
</script>
@@ -76,9 +80,11 @@ const tabsHandleClick = (tab: TabsPaneContext) => {
.svg-icon {
font-size: 18px;
}
.el-dialog__header {
display: none;
}
.el-dialog__footer {
border-top: 1px solid var(--el-border-color);
padding: var(--el-dialog-padding-primary);

View File

@@ -1,7 +1,8 @@
import { IBaseEntity } from "./base"
import { IPmsDept } from "./pms"
/** 项目 */
export interface ICmsProject {
export interface ICmsProject extends IBaseEntity {
/* 项目ID */
projectId: number
/*项目名称*/
@@ -25,3 +26,13 @@ export interface ICmsProject {
/*部门*/
dept: IPmsDept | null
}
/** 项目 */
export interface ICmsDevice extends IBaseEntity {
/* 设备ID */
deviceId: number
/*设备名称*/
deviceName: string
/*状态 【0.禁用 1.正常 2.锁定 】*/
status: number
}

View File

@@ -5,8 +5,8 @@ import { type SidebarOpened, type SidebarClosed } from "@/constants/app-key"
import { type ThemeName } from "@/hooks/useTheme"
import { type TagView } from "@/store/modules/tags-view"
import { type LayoutSettings } from "@/config/layouts"
import { ISimpleProject, type ISimpleTenant } from "@/types/base"
import { defaultProject, defaultTenant } from "@/utils"
import { type ISimpleTenant } from "@/types/base"
import { defaultTenant } from "@/utils"
//#region 系统布局配置
export const getConfigLayout = () => {

View File

@@ -4,6 +4,7 @@ import { ElMessage, ElMessageBox } from "element-plus"
import { get, merge } from "lodash-es"
import { getToken } from "./cache/cookies"
import { useTenantStoreHook } from "@/store/modules/tenant"
import { useProjectStoreHook } from "@/store/modules/project"
import { getEnvBaseURLPrefix } from "."
import { ISimpleTenant } from "@/types/base"
import { defaultTenant } from "@/utils"
@@ -137,15 +138,15 @@ function logout(message: string) {
function createRequest(service: AxiosInstance) {
return function <T>(config: AxiosRequestConfig): Promise<T> {
const tokenValue = getToken()
let tenant: ISimpleTenant = useTenantStoreHook().getCurrentTenant()
if (config.url === "/auth/login") {
tenant = defaultTenant
}
const projectId = useProjectStoreHook().getCurrentProjectId()
// 如果是登录接口,就使用默认的 租户ID 进行登录
const tenantId = config.url === "/auth/login" ? defaultTenant.tenantId : useTenantStoreHook().getCurrentTenantId()
const defaultConfig = {
headers: {
// 携带 Token
Authorization: tokenValue ? tokenValue : undefined,
"x-tenant-id": tenant.tenantId,
"x-tenant-id": tenantId,
"x-project-id": projectId,
"Content-Type": "application/json"
},
timeout: 10000,

View File

@@ -1,12 +1,276 @@
<template>
<div><h1>设备列表</h1></div>
<el-container class="app-container" v-loading="loading">
<el-header>
<el-row :gutter="20">
<el-col :span="8">
<el-input v-model="params.keywords" clearable placeholder="根据设备名称搜索" />
</el-col>
<el-col :span="2">
<el-button type="primary" :icon="Search" @click="searchTableData">搜索</el-button>
</el-col>
<el-col :span="2">
<el-button type="success" :icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="10">
<div class="grid-content ep-bg-purple" />
</el-col>
</el-row>
</el-header>
<el-main>
<el-table :data="tableData">
<el-table-column prop="deviceId" label="设备ID" />
<el-table-column prop="deviceName" label="设备名称" />
<el-table-column prop="status" label="状态">
<template #default="scope">
{{ dictStore.getNormalDisable(scope.row.status) }}
</template>
</el-table-column>
<el-table-column prop="lastUpdateTime" label="修改时间">
<template #default="scope">
<el-tooltip v-if="scope.row.lastUpdateTime" :content="scope.row.lastUpdateTime" placement="top">
<el-link> {{ showTimeAgo(scope.row.lastUpdateTime) }}</el-link>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" width="130">
<template #default="scope">
<el-link :icon="Edit" type="warning" @click="handleEdit(scope.row)">编辑</el-link>
&nbsp;
<el-link :icon="Delete" type="danger" @click="handleDelete(scope.row)">删除</el-link>
</template>
</el-table-column>
</el-table>
<!-- 新增和修改的弹窗 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="30%" :close-on-click-modal="false">
<el-form
ref="entityFormRef"
:model="entityForm"
:rules="entityFormRules"
label-position="right"
label-width="80px"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item prop="deviceName" label="设备名称">
<el-input v-model.trim="entityForm.deviceName" placeholder="设备名称" type="text" tabindex="1" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="status" label="设备状态">
<select-dict-data v-model:value="entityForm.status" dictTypeKey="sys_normal_disable" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveAndFlush">确定</el-button>
</span>
</template>
</el-dialog>
</el-main>
<el-footer>
<el-pagination
v-model:current-page="params.pageIndex"
:page-size="params.pageSize"
:background="true"
layout="sizes, total, prev, pager, next, jumper"
:total="params.pageTotal"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-footer>
</el-container>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue"
const data1 = ref({})
import { ref, reactive, onMounted, computed } from "vue"
import { Plus, Edit, Delete, Search } from "@element-plus/icons-vue"
import { ElMessage, ElMessageBox, FormInstance, FormRules } from "element-plus"
import { queryDeviceApi, saveDeviceApi, updateDeviceApi, deleteDeviceApi } from "@/api/device"
import { cloneDeep } from "lodash-es"
import { ICmsDevice } from "@/types/cms"
import { timeAgo } from "@/utils"
import { useDictStoreHook } from "@/store/modules/dict"
const dictStore = useDictStoreHook()
/** 加载 */
const loading = ref(false)
// true : 新增false : 修改
const saveFlag = ref(false)
const dialogVisible = ref(false)
const dialogTitle = ref("")
const tableData = ref<ICmsDevice[]>()
const params = reactive({
pageTotal: 0,
pageIndex: 1,
pageSize: 10,
keywords: ""
})
/// 表单数据
const entityForm = ref<ICmsDevice>({
deviceId: 0,
deviceName: "",
status: 1
})
const entityFormRef = ref<FormInstance | null>(null)
/// 表单校验规则
const entityFormRules: FormRules = {
deviceName: [{ required: true, message: "请输入设备名称", trigger: "blur" }],
status: [{ required: true, message: "请选择设备状态", trigger: "blur" }]
}
// 获取数据
const getTableData = () => {
loading.value = true
queryDeviceApi(params)
.then((resp) => {
tableData.value = resp.data.list
params.pageTotal = resp.data.total
})
.catch((err) => {
console.log("err :>> ", err)
})
.finally(() => {
loading.value = false
})
}
// 重置 Entity 属性
const resetEntity = () => {
entityForm.value = {
deviceId: 0,
deviceName: "",
status: 1
}
}
const showTimeAgo = computed(() => {
return (value: string) => timeAgo(value)
})
const searchTableData = () => {
params.pageIndex = 1
getTableData()
}
const handleSizeChange = (val: number) => {
params.pageSize = val
getTableData()
}
const handleCurrentChange = (val: number) => {
params.pageIndex = val
getTableData()
}
// 编辑
const handleEdit = (data: ICmsDevice) => {
resetEntity()
entityForm.value = cloneDeep(data)
dialogTitle.value = "修改"
saveFlag.value = false
dialogVisible.value = true
}
// 新增
const handleAdd = () => {
resetEntity()
dialogTitle.value = "新增"
saveFlag.value = true
dialogVisible.value = true
}
// 删除
const handleDelete = (data: ICmsDevice) => {
ElMessageBox.confirm(`确要删除 ${data.deviceName} 吗?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
deleteDeviceApi(data.deviceId)
.then((resp) => {
if (resp.data) {
ElMessage({
message: `删除 ${data.deviceName} 成功!`,
type: "success"
})
}
})
.catch((err) => {
console.log("err :>> ", err)
})
.finally(() => {
getTableData()
})
})
.catch((error) => {
console.log("error :>> ", error)
})
}
// 新增和修改
const handleSaveAndFlush = () => {
entityFormRef.value?.validate((valid: boolean) => {
if (valid) {
loading.value = true
if (saveFlag.value) {
saveDeviceApi(entityForm.value)
.then((resp) => {
if (resp.data) {
ElMessage({
message: "新增设备成功!",
type: "success"
})
}
})
.catch((err) => {
console.log("err :>> ", err)
})
.finally(() => {
loading.value = false
dialogVisible.value = false
getTableData()
})
} else {
updateDeviceApi(entityForm.value)
.then((resp) => {
if (resp.data) {
ElMessage({
message: "修改设备成功!",
type: "success"
})
}
})
.catch((err) => {
console.log("err :>> ", err)
})
.finally(() => {
loading.value = false
dialogVisible.value = false
getTableData()
})
}
} else {
loading.value = false
return false
}
})
}
onMounted(() => {
console.log("data1 :>> ", data1.value)
getTableData()
})
</script>

View File

@@ -75,7 +75,7 @@
<div v-has="['cms_project:reset:password']">
<el-dropdown-item :icon="Link" command="ResetPassword">重置密码</el-dropdown-item>
</div>
<div v-if="scope.row.adminFlag !== 1" v-has="['cms_project:delete']">
<div v-if="scope.row.projectId !== 10001" v-has="['cms_project:delete']">
<el-dropdown-item :icon="Delete" command="Delete">删除</el-dropdown-item>
</div>
</el-dropdown-menu>

View File

@@ -249,7 +249,7 @@ const adminResult = ref<ICreateTenantAdmin>({
const entityFormRef = ref<FormInstance | null>(null)
const tenantIdValidator = (rule: any, value: any, callback: any) => {
if (!/\w{4,12}$/.test(value)) {
if (!/^\w{4,12}$/.test(value)) {
callback(new Error("只能是字母和数字长度4~12位!"))
} else {
callback()

View File

@@ -11,7 +11,9 @@
<el-col :span="2">
<el-button type="success" :icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="10"><div class="grid-content ep-bg-purple" /></el-col>
<el-col :span="10">
<div class="grid-content ep-bg-purple" />
</el-col>
</el-row>
</el-header>
<el-main>
@@ -459,27 +461,36 @@ const handleAdd = () => {
// 删除
const handleDelete = (data: ISysTenant) => {
ElMessageBox.confirm(`确要删除 ${data.name} 吗?`, "警告", {
ElMessageBox.prompt(`请在下方的输入框中填写租户ID`, `确要删除 ${data.name} 吗?`, {
confirmButtonText: "确定",
cancelButtonText: "取消",
inputPattern: /^\w{4,12}$/,
inputErrorMessage: "租户ID可以是字母和数字长度4~12位",
type: "warning"
})
.then(() => {
deleteTenantApi(data.tenantId)
.then((resp) => {
if (resp.data) {
ElMessage({
message: `删除 ${data.name} 成功!`,
type: "success"
})
}
})
.catch((err) => {
console.log("err :>> ", err)
})
.finally(() => {
getTableData()
.then((value) => {
if (value.value === data.tenantId) {
deleteTenantApi(data.tenantId)
.then((resp) => {
if (resp.data) {
ElMessage({
message: `删除 ${data.name} 成功!`,
type: "success"
})
}
})
.catch((err) => {
console.log("err :>> ", err)
})
.finally(() => {
getTableData()
})
} else {
ElMessage({
message: `租户ID输入错误`,
type: "error"
})
}
})
.catch((error) => {
console.log("error :>> ", error)