feat: License

This commit is contained in:
fit2cloud-chenyw
2025-06-19 17:46:46 +08:00
parent e12e9d49d4
commit 6ed75c254f
10 changed files with 407 additions and 3 deletions
+11
View File
@@ -0,0 +1,11 @@
export interface F2CLicense {
isv: string
status: string
corporation: string
expired: string
count: number
version: string
edition: string
serialNo: string
remark: string
}
+282
View File
@@ -0,0 +1,282 @@
<script lang="ts" setup>
import logo from '@/assets/svg/logo.svg'
import aboutBg from '@/assets/img/about-bg.png'
import { ref, reactive, onMounted } from 'vue'
import type { F2CLicense } from './index.ts'
import { licenseApi } from '@/api/license'
import { ElMessage } from 'element-plus-secondary'
import { useI18n } from 'vue-i18n'
//import { useUserStore } from '@/stores/user'
import { useCache } from '@/utils/useCache'
const dialogVisible = ref(false)
const { wsCache } = useCache()
const { t } = useI18n()
//const userStore = useUserStore()
const license: F2CLicense = reactive({
status: '',
corporation: '',
expired: '',
count: 0,
version: '',
edition: '',
serialNo: '',
remark: '',
isv: '',
})
const tipsSuffix = ref('')
const build = ref('')
const isAdmin = ref(true)
const fileList = reactive([])
const dynamicCardClass = ref('')
const loading = ref(false)
onMounted(() => {
// isAdmin.value = userStore.getUid === '1'
initVersion()
getLicenseInfo()
})
const initVersion = () => {
licenseApi.version().then((res) => {
build.value = res
})
}
const beforeUpload = (file: any) => {
importLic(file)
return false
}
const support = () => {
const url = 'https://support.fit2cloud.com/'
const openType = wsCache.get('open-backend') === '1' ? '_self' : '_blank'
window.open(url, openType)
}
const getLicenseInfo = () => {
validateHandler((res: any) => {
const info = getLicense(res)
setLicense(info)
})
}
const setLicense = (lic: any) => {
const lic_obj = {
status: lic.status,
corporation: lic.corporation,
expired: lic.expired,
count: lic.count,
version: lic.version,
edition: lic.edition,
serialNo: lic.serialNo,
remark: lic.remark,
isv: lic.isv,
}
Object.assign(license, lic_obj)
if (license?.serialNo && license?.remark) {
dynamicCardClass.value = 'about-card-max'
} else if (!license?.serialNo && !license?.remark) {
dynamicCardClass.value = ''
} else {
dynamicCardClass.value = 'about-card-medium'
}
}
const removeDistributeModule = () => {
const key = 'xpack-model-distributed'
localStorage.removeItem(key)
}
const importLic = (file: any) => {
removeDistributeModule()
const reader = new FileReader()
reader.onload = function (e: any) {
const licKey = e.target.result
update(licKey)
}
reader.readAsText(file)
}
const validateHandler = (success: any) => {
licenseApi.validate().then(success)
}
const getLicense = (result: any) => {
if (result.status === 'valid') {
tipsSuffix.value = result?.license?.edition === 'Embedded' ? '套' : '个账号'
}
return {
status: result.status,
corporation: result.license ? result.license.corporation : '',
expired: result.license ? result.license.expired : '',
count: result.license ? result.license.count : 0,
version: result.license ? result.license.version : '',
edition: result.license ? result.license.edition : '',
serialNo: result.license ? result.license.serialNo : '',
remark: result.license ? result.license.remark : '',
isv: result.license ? result.license.isv : '',
}
}
const update = (licKey: string) => {
const param = { license_key: licKey }
loading.value = true
licenseApi.update(param).then((response: any) => {
loading.value = false
if (response.status === 'valid') {
ElMessage.success(t('about.update_success'))
const info = getLicense(response)
setLicense(info)
} else {
ElMessage.warning(response.message)
}
})
}
const open = () => {
dialogVisible.value = true
getLicenseInfo()
}
defineExpose({
open,
})
</script>
<template>
<el-dialog
v-model="dialogVisible"
:append-to-body="true"
:title="t('about.title')"
width="840px"
class="about-dialog"
>
<img width="792" height="180" :src="aboutBg" />
<div class="color-overlay"></div>
<el-icon class="logo">
<icon name="logo"><logo class="svg-icon" /></icon>
</el-icon>
<div class="content">
<div class="item">
<div class="label">{{ $t('about.auth_to') }}</div>
<div class="value">{{ license.corporation }}</div>
</div>
<div v-if="license.isv" class="item">
<div class="label">ISV</div>
<div class="value">{{ license.isv }}</div>
</div>
<div class="item">
<div class="label">{{ $t('about.expiration_time') }}</div>
<div class="value" :class="{ 'expired-mark': license.status === 'expired' }">
{{ license.expired }}
</div>
</div>
<div class="item">
<div class="label">{{ $t('about.auth_num') }}</div>
<div class="value">
{{ license.status === 'valid' ? `${license.count} ${tipsSuffix}` : '' }}
</div>
</div>
<div class="item">
<div class="label">{{ $t('about.version') }}</div>
<div class="value">
{{
!license?.edition
? $t('about.standard')
: license.edition === 'Embedded'
? $t('about.Embedded')
: license.edition === 'Professional'
? $t('about.Professional')
: $t('about.enterprise')
}}
</div>
</div>
<div class="item">
<div class="label">{{ $t('about.version_num') }}</div>
<div class="value">{{ build }}</div>
</div>
<div class="item">
<div class="label">{{ $t('about.serial_no') }}</div>
<div class="value">{{ license.serialNo || '-' }}</div>
</div>
<div class="item">
<div class="label">{{ $t('about.remark') }}</div>
<div class="value ellipsis">{{ license.remark || '-' }}</div>
</div>
<div v-if="isAdmin" style="margin-top: 24px" class="lic_rooter">
<el-upload
action=""
:multiple="false"
:show-file-list="false"
:file-list="fileList"
accept=".key"
name="file"
:before-upload="beforeUpload"
>
<el-button plain> {{ $t('about.update_license') }} </el-button>
</el-upload>
<el-button plain @click="support"> {{ $t('about.support') }} </el-button>
</div>
</div>
</el-dialog>
</template>
<style lang="less">
.about-dialog {
img {
border-radius: 4px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.color-overlay {
position: absolute;
border-radius: 4px;
width: 792px;
height: 180px;
top: 72px;
background-color: #7394f0;
mix-blend-mode: multiply;
}
.logo {
font-size: 400px;
position: absolute;
top: -40px;
left: 228px;
color: #fff;
}
.content {
border-radius: 4px;
border: 1px solid #dee0e3;
border-top-left-radius: 0;
border-top-right-radius: 0;
padding: 24px 40px;
margin-top: -7px;
.item {
font-family: var(--de-custom_font, 'PingFang');
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
margin-bottom: 16px;
display: flex;
.expired-mark {
color: red;
}
.label {
color: #646a73;
width: 300px;
}
.value {
margin-left: 24px;
max-width: 388px;
}
}
}
}
.lic_rooter {
flex-direction: row;
box-sizing: border-box;
display: flex;
align-items: center;
align-content: center;
width: fit-content;
justify-content: space-between;
column-gap: 12px;
}
</style>
+8 -1
View File
@@ -84,6 +84,7 @@
<el-dropdown-item>
<language-selector />
</el-dropdown-item>
<el-dropdown-item @click="toAbout">About</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -116,6 +117,7 @@
<el-dropdown-item>
<language-selector />
</el-dropdown-item>
<el-dropdown-item @click="toAbout">About</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -153,6 +155,7 @@
</div>
</div>
</div>
<AboutDialog ref="aboutRef" />
</template>
<script lang="ts" setup>
@@ -170,7 +173,9 @@ import { ArrowLeftBold } from '@element-plus/icons-vue'
import { useCache } from '@/utils/useCache'
import { useI18n } from 'vue-i18n'
import LanguageSelector from '@/components/Language-selector/index.vue'
import AboutDialog from '@/components/about/index.vue'
const aboutRef = ref()
const { t } = useI18n()
const { wsCache } = useCache()
const topLayout = ref(false)
@@ -244,7 +249,9 @@ const switchLayout = () => {
topLayout.value = !topLayout.value
wsCache.set('sqlbot-topbar-layout', topLayout.value)
}
const toAbout = () => {
aboutRef.value?.open()
}
onMounted(() => {
topLayout.value = wsCache.get('sqlbot-topbar-layout') || true
})