mirror of
https://github.com/thousmile/molly-multi-tenant.git
synced 2025-12-30 04:32:26 +00:00
1.切换租户-切换项目
2.添加 设备模板
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
> {%
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.*;
|
||||
|
||||
@@ -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
33
web-ui/src/api/device.ts
Normal 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}`)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
17
web-ui/src/hooks/useTenantAndProject.ts
Normal file
17
web-ui/src/hooks/useTenantAndProject.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
4
web-ui/src/utils/cache/local-storage.ts
vendored
4
web-ui/src/utils/cache/local-storage.ts
vendored
@@ -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 = () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user