mirror of
https://github.com/thousmile/molly-multi-tenant.git
synced 2025-12-30 04:32:26 +00:00
1.修复 平台 搜索租户接口bug
2.平台页面 添加生成租户系统URL,和二维码!
This commit is contained in:
@@ -2,3 +2,6 @@
|
||||
|
||||
## 项目标题
|
||||
VITE_APP_TITLE = "Molly平台管理系统"
|
||||
|
||||
## 租户系统域名链接。如: molly.xaaef.com 或者 .xaaef.com
|
||||
VITE_TENANT_LINK = "molly.xaaef.com:8555/tenant/"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 开发环境自定义的环境变量(命名必须以 VITE_ 开头)
|
||||
|
||||
## 后端接口公共路径(如果解决跨域问题采用反向代理就只需写公共路径)
|
||||
VITE_BASE_API = 'http://localhost:18891'
|
||||
VITE_BASE_API = 'http://127.0.0.1:18891'
|
||||
|
||||
## 路由模式 hash 或 html5
|
||||
VITE_ROUTER_HISTORY = 'hash'
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"path-browserify": "1.0.1",
|
||||
"path-to-regexp": "6.2.1",
|
||||
"pinia": "2.1.7",
|
||||
"qrcode-vue3": "^1.6.8",
|
||||
"screenfull": "6.0.2",
|
||||
"sockjs-client": "^1.6.1",
|
||||
"uuid": "^9.0.1",
|
||||
|
||||
13
platform-web-ui/pnpm-lock.yaml
generated
13
platform-web-ui/pnpm-lock.yaml
generated
@@ -50,6 +50,9 @@ dependencies:
|
||||
pinia:
|
||||
specifier: 2.1.7
|
||||
version: 2.1.7(typescript@5.2.2)(vue@3.3.5)
|
||||
qrcode-vue3:
|
||||
specifier: ^1.6.8
|
||||
version: 1.6.8
|
||||
screenfull:
|
||||
specifier: 6.0.2
|
||||
version: 6.0.2
|
||||
@@ -4179,6 +4182,16 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/qrcode-generator@1.4.4:
|
||||
resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==}
|
||||
dev: false
|
||||
|
||||
/qrcode-vue3@1.6.8:
|
||||
resolution: {integrity: sha512-LtMnwKWi58ZqHbXBcsTF/VxDYhI6RrBIrDQw8fbDVlO8p5tJBZa7TaIaVYLY937vKO2WCEBmOKksGlpm5ccEIg==}
|
||||
dependencies:
|
||||
qrcode-generator: 1.4.4
|
||||
dev: false
|
||||
|
||||
/query-string@4.3.4:
|
||||
resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
1
platform-web-ui/src/icons/svg/qrcode.svg
Normal file
1
platform-web-ui/src/icons/svg/qrcode.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1700031003005" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6004" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M384 64l-249.6 0c-51.2 0-89.6 41.6-89.6 89.6l0 227.2c0 51.2 41.6 89.6 89.6 89.6l249.6 0c51.2 0 89.6-41.6 89.6-89.6l0-227.2C473.6 105.6 435.2 64 384 64zM428.8 380.8c0 25.6-19.2 44.8-44.8 44.8l-249.6 0c-25.6 0-44.8-19.2-44.8-44.8l0-227.2c0-25.6 19.2-44.8 44.8-44.8l249.6 0c25.6 0 44.8 19.2 44.8 44.8L428.8 380.8z" p-id="6005"></path><path d="M192 192l134.4 0 0 134.4-134.4 0 0-134.4Z" p-id="6006"></path><path d="M377.6 544l-243.2 0c-48 0-86.4 38.4-86.4 89.6l0 220.8c0 48 38.4 89.6 86.4 89.6l243.2 0c48 0 86.4-38.4 86.4-89.6l0-220.8C467.2 582.4 425.6 544 377.6 544zM422.4 851.2c0 25.6-19.2 44.8-44.8 44.8l-243.2 0c-25.6 0-44.8-19.2-44.8-44.8l0-220.8c0-25.6 19.2-44.8 44.8-44.8l243.2 0c25.6 0 44.8 19.2 44.8 44.8L422.4 851.2z" p-id="6007"></path><path d="M192 668.8l131.2 0 0 131.2-131.2 0 0-131.2Z" p-id="6008"></path><path d="M633.6 470.4l249.6 0c51.2 0 89.6-41.6 89.6-89.6l0-227.2c0-51.2-41.6-89.6-89.6-89.6l-249.6 0c-51.2 0-89.6 41.6-89.6 89.6l0 227.2C544 432 585.6 470.4 633.6 470.4zM588.8 153.6c0-25.6 19.2-44.8 44.8-44.8l249.6 0c25.6 0 44.8 19.2 44.8 44.8l0 227.2c0 25.6-19.2 44.8-44.8 44.8l-249.6 0c-25.6 0-44.8-19.2-44.8-44.8L588.8 153.6z" p-id="6009"></path><path d="M700.8 192l134.4 0 0 134.4-134.4 0 0-134.4Z" p-id="6010"></path><path d="M572.8 716.8l137.6 0c12.8 0 22.4-9.6 22.4-22.4l0-137.6c0-12.8-9.6-22.4-22.4-22.4l-137.6 0c-12.8 0-22.4 9.6-22.4 22.4l0 137.6C550.4 707.2 560 716.8 572.8 716.8z" p-id="6011"></path><path d="M886.4 563.2l0 38.4c0 12.8 12.8 25.6 25.6 25.6l38.4 0c12.8 0 25.6-12.8 25.6-25.6l0-38.4c0-12.8-12.8-25.6-25.6-25.6l-38.4 0C899.2 537.6 886.4 547.2 886.4 563.2z" p-id="6012"></path><path d="M582.4 944l48 0c12.8 0 22.4-9.6 22.4-22.4l0-48c0-12.8-9.6-22.4-22.4-22.4l-48 0c-12.8 0-22.4 9.6-22.4 22.4l0 48C560 934.4 569.6 944 582.4 944z" p-id="6013"></path><path d="M944 704l-99.2 0c-16 0-28.8 12.8-28.8 28.8l0 44.8-48 0c-19.2 0-32 12.8-32 32l0 99.2c0 16 12.8 28.8 28.8 28.8l179.2 3.2c16 0 28.8-12.8 28.8-28.8l0-179.2C972.8 716.8 960 704 944 704z" p-id="6014"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -2,6 +2,8 @@ import dayjs from "dayjs"
|
||||
import { removeConfigLayout } from "@/utils/cache/local-storage"
|
||||
import chinaAreaJson from "@/assets/ChinaArea.json"
|
||||
import { ISimpleProject, ISimpleTenant } from "@/types/base"
|
||||
import { getFileBlob, httpGet } from "./service"
|
||||
import { AxiosResponse } from "axios"
|
||||
|
||||
//#region 格式化日期时间
|
||||
export const DEFAULT_DATE_TIME_PATTERN = "YYYY-MM-DD HH:mm:ss"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useProjectStoreHook } from "@/store/modules/project"
|
||||
import { getEnvBaseURLPrefix } from "."
|
||||
import { ISimpleProject, ISimpleTenant } from "@/types/base"
|
||||
import { defaultTenant } from "@/utils"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
|
||||
/** 创建请求实例 */
|
||||
function createService() {
|
||||
@@ -26,7 +27,7 @@ function createService() {
|
||||
const apiData = response.data
|
||||
// 二进制数据则直接返回
|
||||
const responseType = response.request?.responseType
|
||||
if (responseType === "blob" || responseType === "arraybuffer") return apiData
|
||||
if (responseType === "blob" || responseType === "arraybuffer") return response
|
||||
// 这个 code 是和后端约定的业务 code
|
||||
const code = apiData.status
|
||||
// 如果没有 code, 代表这不是项目后端开发的 api
|
||||
@@ -217,4 +218,41 @@ function httpDelete<T, P>(url: string, params?: T): Promise<P> {
|
||||
return httpRequest<P>({ method: "delete", url, params })
|
||||
}
|
||||
|
||||
export { httpRequest, httpGet, httpPost, httpPut, httpDelete, axiosRequest }
|
||||
/** 单独抽离的 文件下载 工具函数 */
|
||||
async function downloadFile(url: string, fileName?: string): Promise<void> {
|
||||
const { data, headers } = await axios.get(url, { responseType: "blob" })
|
||||
if (!fileName) {
|
||||
const fn1 = headers["content-disposition"]
|
||||
if (fn1) {
|
||||
fileName = fn1.replace(/\w+;filename=(.*)/, "$1")
|
||||
} else {
|
||||
fileName = uuidv4().replace(/-/g, "")
|
||||
}
|
||||
}
|
||||
const urlObject = window.URL || window.webkitURL
|
||||
const downloadUrl = urlObject.createObjectURL(data)
|
||||
const link = document.createElement("a")
|
||||
link.href = downloadUrl
|
||||
link.download = fileName!
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
urlObject.revokeObjectURL(downloadUrl)
|
||||
}
|
||||
|
||||
/** 单独抽离的 文件下载 工具函数 */
|
||||
function getFileBlob(url: string): Promise<Blob> {
|
||||
// 单独处理自定义请求/响应回掉
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(url, { responseType: "blob" })
|
||||
.then((resp) => {
|
||||
const { data, headers } = resp
|
||||
const blob = new Blob([data], { type: headers["content-type"] })
|
||||
resolve(blob)
|
||||
})
|
||||
.catch((error) => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
export { httpRequest, httpGet, httpPost, httpPut, httpDelete, axiosRequest, downloadFile, getFileBlob }
|
||||
|
||||
@@ -91,7 +91,11 @@
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="!isDefaultTenantId(scope.row.tenantId)" :icon="Link" command="ResetData">
|
||||
<el-dropdown-item :icon="Link" command="Jump">跳转客户系统</el-dropdown-item>
|
||||
<el-dropdown-item command="QRcode">
|
||||
<template #default><SvgIcon name="qrcode" /> 生成二维码</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="!isDefaultTenantId(scope.row.tenantId)" :icon="Refresh" command="ResetData">
|
||||
重置数据
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="!isDefaultTenantId(scope.row.tenantId)" :icon="Delete" command="Delete">
|
||||
@@ -118,6 +122,24 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 生成二维码弹窗 -->
|
||||
<el-dialog v-model="qrCodeDialog.visible" :title="qrCodeDialog.title" width="500px">
|
||||
<div style="text-align: center">
|
||||
<el-link type="primary" :href="qrCodeDialog.link" target="_blank">
|
||||
{{ qrCodeDialog.link }}
|
||||
</el-link>
|
||||
<br />
|
||||
<br />
|
||||
<QRCodeVue3
|
||||
:key="qrCodeDialog.tenantId"
|
||||
:width="300"
|
||||
:value="qrCodeDialog.link"
|
||||
:imageOptions="{ hideBackgroundDots: true, imageSize: 0.4, margin: 0 }"
|
||||
:image="qrCodeDialog.logo"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 新增和修改的弹窗 -->
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="50%" :close-on-click-modal="false">
|
||||
<template v-if="saveFlag">
|
||||
@@ -251,7 +273,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed, watch } from "vue"
|
||||
import { Plus, Edit, Delete, Search, UserFilled, Loading, Link } from "@element-plus/icons-vue"
|
||||
import { Plus, Edit, Delete, Search, UserFilled, Loading, Link, Refresh } from "@element-plus/icons-vue"
|
||||
import { ElMessage, ElMessageBox, FormInstance, FormRules } from "element-plus"
|
||||
import { queryTenantApi, updateTenantApi, deleteTenantApi, resetDataTenantApi } from "@/api/tenant"
|
||||
import { listTemplateApi } from "@/api/template"
|
||||
@@ -265,6 +287,9 @@ import { showExpiredDateAgo, showStringOverflow, showChinaArea, isDefaultTenantI
|
||||
import { isEmail, isTelphone } from "@/utils/validate"
|
||||
import { futureShortcuts } from "@/utils"
|
||||
import { useNoticeStoreHook } from "@/store/modules/notice"
|
||||
import QRCodeVue3 from "qrcode-vue3"
|
||||
import logoPng from "@/assets/layouts/logo.png?url"
|
||||
import { downloadFile, getFileBlob } from "@/utils/service"
|
||||
|
||||
const dictStore = useDictStoreHook()
|
||||
|
||||
@@ -470,11 +495,47 @@ const handleCommand = (command: string, data: ISysTenant) => {
|
||||
case "Delete":
|
||||
handleDelete(data)
|
||||
break
|
||||
case "Jump":
|
||||
window.open(genTenantLink(data.tenantId), "_blank")
|
||||
break
|
||||
case "QRcode":
|
||||
getFileBlob(data.logo)
|
||||
.then((blob) => {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(blob)
|
||||
reader.onload = () => {
|
||||
qrCodeDialog.value.title = `扫码进入 ${data.name} 客户管理系统`
|
||||
qrCodeDialog.value.link = genTenantLink(data.tenantId)
|
||||
qrCodeDialog.value.tenantId = data.tenantId
|
||||
const base64 = reader.result as string
|
||||
qrCodeDialog.value.logo = base64
|
||||
qrCodeDialog.value.visible = true
|
||||
}
|
||||
reader.onerror = (err) => console.log("err :>> ", err)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("QRcode error :>> ", err)
|
||||
})
|
||||
break
|
||||
default:
|
||||
console.log("command :>> ", command)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const qrCodeDialog = ref({
|
||||
visible: false,
|
||||
title: "",
|
||||
tenantId: "",
|
||||
link: "",
|
||||
logo: ""
|
||||
})
|
||||
|
||||
// 生成租户二级域名跳转链接
|
||||
const genTenantLink = (tenantId: string) => {
|
||||
return `http://${tenantId}.${import.meta.env.VITE_TENANT_LINK}`
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = (data: ISysTenant) => {
|
||||
ElMessageBox.prompt(`请在下方的输入框中填写租户ID`, `确要删除 ${data.name} 吗?`, {
|
||||
|
||||
1
platform-web-ui/types/env.d.ts
vendored
1
platform-web-ui/types/env.d.ts
vendored
@@ -2,6 +2,7 @@
|
||||
declare interface ImportMetaEnv {
|
||||
readonly VITE_APP_TITLE: string
|
||||
readonly VITE_BASE_API: string
|
||||
readonly VITE_TENANT_LINK: string
|
||||
readonly VITE_ROUTER_HISTORY: "hash" | "html5"
|
||||
readonly VITE_PUBLIC_PATH: string
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class JwtConst {
|
||||
String.format("%s/**", STOMP_ENDPOINT),
|
||||
"/actuator/**",
|
||||
// 判断租户是否存在
|
||||
"/sys/tenant/simple/*",
|
||||
"/sys/tenant/simple",
|
||||
"/v2/api-docs",
|
||||
"/v3/api-docs/**",
|
||||
"/doc.html",
|
||||
|
||||
@@ -39,8 +39,8 @@ public class SysTenantController {
|
||||
|
||||
|
||||
@Operation(summary = "简单Id查询", description = "根据Id查询 最基础的字段")
|
||||
@GetMapping("/simple/{id}")
|
||||
public JsonResult<SysTenant> findSimpleById(@PathVariable("id") String id) {
|
||||
@GetMapping("/simple")
|
||||
public JsonResult<SysTenant> findSimpleById(@RequestParam("tenantId") String id) {
|
||||
return JsonResult.success(baseService.getSimpleById(id));
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<hutool.version>5.8.22</hutool.version>
|
||||
<knife4j.version>4.3.0</knife4j.version>
|
||||
<mybatis-plus.version>3.5.4</mybatis-plus.version>
|
||||
<mybatis-plus.version>3.5.4.1</mybatis-plus.version>
|
||||
|
||||
<spring-boot.version>3.1.5</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
@@ -19,7 +19,8 @@ export const existTenantApi = (id: string) => {
|
||||
|
||||
/** 根据Id简单查询 */
|
||||
export const getSimpleTenantApi = (id: string) => {
|
||||
return httpGet<string, IJsonResult<ISimpleTenant>>(`/sys/tenant/simple/${id}`)
|
||||
const params = { tenantId: id }
|
||||
return httpGet<any, IJsonResult<ISimpleTenant>>("/sys/tenant/simple", params)
|
||||
}
|
||||
|
||||
/** 分页查询所有 */
|
||||
|
||||
Reference in New Issue
Block a user