Skip to content

Commit 6ed75c2

Browse files
feat: License
1 parent e12e9d4 commit 6ed75c2

File tree

10 files changed

+407
-3
lines changed

10 files changed

+407
-3
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""011_license_ddl
2+
3+
Revision ID: a3af70d43e98
4+
Revises: 8dc3b1bdbfef
5+
Create Date: 2025-06-18 16:09:33.896600
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'a3af70d43e98'
14+
down_revision = '8dc3b1bdbfef'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
op.create_table(
21+
'license',
22+
sa.Column('id', sa.BigInteger(), primary_key=True, nullable=False),
23+
sa.Column('license_key', sa.Text(), default="", nullable=False),
24+
sa.Column('f2c_license', sa.Text(), default="", nullable=False),
25+
sa.Column('create_time', sa.BigInteger(), default=0, nullable=False),
26+
sa.Column('update_time', sa.BigInteger(), default=0, nullable=False)
27+
)
28+
op.create_index(op.f('ix_license_id'), 'license', ['id'], unique=False)
29+
30+
31+
def downgrade():
32+
op.drop_table('license')
33+
op.drop_index(op.f('ix_license_id'), table_name='license')

backend/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from alembic.config import Config
1616
from alembic import command
1717
from fastapi_mcp import FastApiMCP
18-
18+
import sqlbot_xpack
1919

2020
def run_migrations():
2121
alembic_cfg = Config("alembic.ini")
@@ -88,6 +88,7 @@ async def read_index():
8888

8989
mcp.setup_server()
9090

91+
sqlbot_xpack.init_fastapi_app(app)
9192
if __name__ == "__main__":
9293
import uvicorn
9394

frontend/src/api/license.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { request } from '@/utils/request'
2+
3+
export const licenseApi = {
4+
validate: () => request.get('/system/license'),
5+
version: () => request.get('/system/license/version'),
6+
update: (data: any) => request.post('/system/license', data),
7+
}
167 KB
Loading

frontend/src/assets/svg/logo.svg

Lines changed: 21 additions & 0 deletions
Loading
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export interface F2CLicense {
2+
isv: string
3+
status: string
4+
corporation: string
5+
expired: string
6+
count: number
7+
version: string
8+
edition: string
9+
serialNo: string
10+
remark: string
11+
}
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
<script lang="ts" setup>
2+
import logo from '@/assets/svg/logo.svg'
3+
import aboutBg from '@/assets/img/about-bg.png'
4+
import { ref, reactive, onMounted } from 'vue'
5+
import type { F2CLicense } from './index.ts'
6+
import { licenseApi } from '@/api/license'
7+
import { ElMessage } from 'element-plus-secondary'
8+
import { useI18n } from 'vue-i18n'
9+
//import { useUserStore } from '@/stores/user'
10+
import { useCache } from '@/utils/useCache'
11+
const dialogVisible = ref(false)
12+
const { wsCache } = useCache()
13+
const { t } = useI18n()
14+
//const userStore = useUserStore()
15+
const license: F2CLicense = reactive({
16+
status: '',
17+
corporation: '',
18+
expired: '',
19+
count: 0,
20+
version: '',
21+
edition: '',
22+
serialNo: '',
23+
remark: '',
24+
isv: '',
25+
})
26+
const tipsSuffix = ref('')
27+
const build = ref('')
28+
const isAdmin = ref(true)
29+
const fileList = reactive([])
30+
const dynamicCardClass = ref('')
31+
const loading = ref(false)
32+
onMounted(() => {
33+
// isAdmin.value = userStore.getUid === '1'
34+
initVersion()
35+
getLicenseInfo()
36+
})
37+
38+
const initVersion = () => {
39+
licenseApi.version().then((res) => {
40+
build.value = res
41+
})
42+
}
43+
const beforeUpload = (file: any) => {
44+
importLic(file)
45+
return false
46+
}
47+
48+
const support = () => {
49+
const url = 'https://support.fit2cloud.com/'
50+
const openType = wsCache.get('open-backend') === '1' ? '_self' : '_blank'
51+
window.open(url, openType)
52+
}
53+
54+
const getLicenseInfo = () => {
55+
validateHandler((res: any) => {
56+
const info = getLicense(res)
57+
setLicense(info)
58+
})
59+
}
60+
const setLicense = (lic: any) => {
61+
const lic_obj = {
62+
status: lic.status,
63+
corporation: lic.corporation,
64+
expired: lic.expired,
65+
count: lic.count,
66+
version: lic.version,
67+
edition: lic.edition,
68+
serialNo: lic.serialNo,
69+
remark: lic.remark,
70+
isv: lic.isv,
71+
}
72+
Object.assign(license, lic_obj)
73+
if (license?.serialNo && license?.remark) {
74+
dynamicCardClass.value = 'about-card-max'
75+
} else if (!license?.serialNo && !license?.remark) {
76+
dynamicCardClass.value = ''
77+
} else {
78+
dynamicCardClass.value = 'about-card-medium'
79+
}
80+
}
81+
const removeDistributeModule = () => {
82+
const key = 'xpack-model-distributed'
83+
localStorage.removeItem(key)
84+
}
85+
const importLic = (file: any) => {
86+
removeDistributeModule()
87+
const reader = new FileReader()
88+
reader.onload = function (e: any) {
89+
const licKey = e.target.result
90+
update(licKey)
91+
}
92+
reader.readAsText(file)
93+
}
94+
const validateHandler = (success: any) => {
95+
licenseApi.validate().then(success)
96+
}
97+
const getLicense = (result: any) => {
98+
if (result.status === 'valid') {
99+
tipsSuffix.value = result?.license?.edition === 'Embedded' ? '' : '个账号'
100+
}
101+
return {
102+
status: result.status,
103+
corporation: result.license ? result.license.corporation : '',
104+
expired: result.license ? result.license.expired : '',
105+
count: result.license ? result.license.count : 0,
106+
version: result.license ? result.license.version : '',
107+
edition: result.license ? result.license.edition : '',
108+
serialNo: result.license ? result.license.serialNo : '',
109+
remark: result.license ? result.license.remark : '',
110+
isv: result.license ? result.license.isv : '',
111+
}
112+
}
113+
const update = (licKey: string) => {
114+
const param = { license_key: licKey }
115+
loading.value = true
116+
licenseApi.update(param).then((response: any) => {
117+
loading.value = false
118+
if (response.status === 'valid') {
119+
ElMessage.success(t('about.update_success'))
120+
const info = getLicense(response)
121+
setLicense(info)
122+
} else {
123+
ElMessage.warning(response.message)
124+
}
125+
})
126+
}
127+
128+
const open = () => {
129+
dialogVisible.value = true
130+
getLicenseInfo()
131+
}
132+
133+
defineExpose({
134+
open,
135+
})
136+
</script>
137+
138+
<template>
139+
<el-dialog
140+
v-model="dialogVisible"
141+
:append-to-body="true"
142+
:title="t('about.title')"
143+
width="840px"
144+
class="about-dialog"
145+
>
146+
<img width="792" height="180" :src="aboutBg" />
147+
<div class="color-overlay"></div>
148+
<el-icon class="logo">
149+
<icon name="logo"><logo class="svg-icon" /></icon>
150+
</el-icon>
151+
<div class="content">
152+
<div class="item">
153+
<div class="label">{{ $t('about.auth_to') }}</div>
154+
<div class="value">{{ license.corporation }}</div>
155+
</div>
156+
<div v-if="license.isv" class="item">
157+
<div class="label">ISV</div>
158+
<div class="value">{{ license.isv }}</div>
159+
</div>
160+
<div class="item">
161+
<div class="label">{{ $t('about.expiration_time') }}</div>
162+
<div class="value" :class="{ 'expired-mark': license.status === 'expired' }">
163+
{{ license.expired }}
164+
</div>
165+
</div>
166+
<div class="item">
167+
<div class="label">{{ $t('about.auth_num') }}</div>
168+
<div class="value">
169+
{{ license.status === 'valid' ? `${license.count} ${tipsSuffix}` : '' }}
170+
</div>
171+
</div>
172+
<div class="item">
173+
<div class="label">{{ $t('about.version') }}</div>
174+
<div class="value">
175+
{{
176+
!license?.edition
177+
? $t('about.standard')
178+
: license.edition === 'Embedded'
179+
? $t('about.Embedded')
180+
: license.edition === 'Professional'
181+
? $t('about.Professional')
182+
: $t('about.enterprise')
183+
}}
184+
</div>
185+
</div>
186+
<div class="item">
187+
<div class="label">{{ $t('about.version_num') }}</div>
188+
<div class="value">{{ build }}</div>
189+
</div>
190+
<div class="item">
191+
<div class="label">{{ $t('about.serial_no') }}</div>
192+
<div class="value">{{ license.serialNo || '-' }}</div>
193+
</div>
194+
<div class="item">
195+
<div class="label">{{ $t('about.remark') }}</div>
196+
<div class="value ellipsis">{{ license.remark || '-' }}</div>
197+
</div>
198+
199+
<div v-if="isAdmin" style="margin-top: 24px" class="lic_rooter">
200+
<el-upload
201+
action=""
202+
:multiple="false"
203+
:show-file-list="false"
204+
:file-list="fileList"
205+
accept=".key"
206+
name="file"
207+
:before-upload="beforeUpload"
208+
>
209+
<el-button plain> {{ $t('about.update_license') }} </el-button>
210+
</el-upload>
211+
<el-button plain @click="support"> {{ $t('about.support') }} </el-button>
212+
</div>
213+
</div>
214+
</el-dialog>
215+
</template>
216+
217+
<style lang="less">
218+
.about-dialog {
219+
img {
220+
border-radius: 4px;
221+
border-bottom-left-radius: 0;
222+
border-bottom-right-radius: 0;
223+
}
224+
.color-overlay {
225+
position: absolute;
226+
border-radius: 4px;
227+
width: 792px;
228+
height: 180px;
229+
top: 72px;
230+
background-color: #7394f0;
231+
mix-blend-mode: multiply;
232+
}
233+
.logo {
234+
font-size: 400px;
235+
position: absolute;
236+
top: -40px;
237+
left: 228px;
238+
color: #fff;
239+
}
240+
241+
.content {
242+
border-radius: 4px;
243+
border: 1px solid #dee0e3;
244+
border-top-left-radius: 0;
245+
border-top-right-radius: 0;
246+
padding: 24px 40px;
247+
margin-top: -7px;
248+
249+
.item {
250+
font-family: var(--de-custom_font, 'PingFang');
251+
font-size: 16px;
252+
font-style: normal;
253+
font-weight: 400;
254+
line-height: 24px;
255+
margin-bottom: 16px;
256+
display: flex;
257+
.expired-mark {
258+
color: red;
259+
}
260+
.label {
261+
color: #646a73;
262+
width: 300px;
263+
}
264+
265+
.value {
266+
margin-left: 24px;
267+
max-width: 388px;
268+
}
269+
}
270+
}
271+
}
272+
.lic_rooter {
273+
flex-direction: row;
274+
box-sizing: border-box;
275+
display: flex;
276+
align-items: center;
277+
align-content: center;
278+
width: fit-content;
279+
justify-content: space-between;
280+
column-gap: 12px;
281+
}
282+
</style>

0 commit comments

Comments
 (0)