Files
webman-buildadmin-mall/web/src/views/backend/module/index.ts
2026-03-18 17:19:03 +08:00

605 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ElNotification } from 'element-plus'
import { isArray } from 'lodash-es'
import { state } from './store'
import { moduleInstallState, type moduleState } from './types'
import {
changeState,
createOrder,
getInstallState,
index,
info,
modules,
payCheck,
payOrder,
postInstallModule,
preDownload,
} from '/@/api/backend/module'
import { i18n } from '/@/lang/index'
import router from '/@/router/index'
import { useBaAccount } from '/@/stores/baAccount'
import { SYSTEM_ZINDEX } from '/@/stores/constant/common'
import { taskStatus } from '/@/stores/constant/terminalTaskStatus'
import type { UserInfo } from '/@/stores/interface'
import { useTerminal } from '/@/stores/terminal'
import { fullUrl } from '/@/utils/common'
import { uuid } from '/@/utils/random'
import { changeListenDirtyFileSwitch, closeHotUpdate } from '/@/utils/vite'
export const loadData = () => {
state.loading.table = true
if (!state.table.indexLoaded) {
loadIndex().then(() => {
getModules()
})
} else {
getModules()
}
}
export const onRefreshTableData = () => {
state.table.indexLoaded = false
for (const key in state.table.modulesEbak) {
state.table.modulesEbak[key] = undefined
}
loadData()
}
const loadIndex = () => {
return index().then((res) => {
state.table.indexLoaded = true
state.sysVersion = res.data.sysVersion
state.nuxtVersion = res.data.nuxtVersion
state.installedModule = res.data.installed
const installedModuleUids: string[] = []
const installedModuleVersions: { uid: string; version: string }[] = []
if (res.data.installed) {
state.installedModule.forEach((item) => {
installedModuleUids.push(item.uid)
installedModuleVersions.push({
uid: item.uid,
version: item.version,
})
})
state.installedModuleUids = installedModuleUids
state.installedModuleVersions = installedModuleVersions
}
})
}
const getModules = () => {
if (typeof state.table.modulesEbak[state.table.params.activeTab] != 'undefined') {
state.table.modules[state.table.params.activeTab] = modulesOnlyLocalHandle(state.table.modulesEbak[state.table.params.activeTab])
state.loading.table = false
return
}
const params: anyObj = {}
for (const key in state.table.params) {
if (state.table.params[key] != '') {
params[key] = state.table.params[key]
}
}
const moduleUids: string[] = []
params['installed'] = state.installedModuleVersions
params['sysVersion'] = state.sysVersion
modules(params)
.then((res) => {
if (params.activeTab == 'all') {
res.data.rows.forEach((item: anyObj) => {
moduleUids.push(item.uid)
})
state.installedModule.forEach((item) => {
if (moduleUids.indexOf(item.uid) === -1) {
if (state.table.params.quickSearch) {
if (item.title.includes(state.table.params.quickSearch)) res.data.rows.push(item)
} else {
res.data.rows.push(item)
}
}
})
}
state.table.remark = res.data.remark
state.table.modulesEbak[params.activeTab] = res.data.rows.map((item: anyObj) => {
const idx = state.installedModuleUids.indexOf(item.uid)
if (idx !== -1) {
item.state = state.installedModule[idx].state
item.title = state.installedModule[idx].title
item.version = state.installedModule[idx].version
item.website = state.installedModule[idx].website
item.stateTag = moduleStatus(item.state)
if (!isArray(item.tags)) item.tags = []
item.tags.push({
name: `${i18n.global.t('module.installed')} v${state.installedModule[idx].version}`,
type: 'primary',
})
} else {
item.state = 0
}
if (item.new_version && item.tags) {
item.tags.push({
name: i18n.global.t('module.New version'),
type: 'danger',
})
}
return item
})
state.table.modules[params.activeTab] = modulesOnlyLocalHandle(state.table.modulesEbak[params.activeTab])
state.table.category = res.data.category
})
.finally(() => {
state.loading.table = false
})
}
export const showInfo = (uid: string) => {
state.dialog.goodsInfo = true
state.loading.goodsInfo = true
const localItem = state.installedModule.find((item) => {
return item.uid == uid
})
info({
uid: uid,
localVersion: localItem?.version,
sysVersion: state.sysVersion,
})
.then((res) => {
if (localItem) {
if (res.data.info.type == 'local') {
res.data.info = localItem
res.data.info.images = [fullUrl('/static/images/local-module-logo.png')]
res.data.info.type = 'local' // 纯本地模块
} else {
res.data.info.type = 'online'
res.data.info.state = localItem.state
res.data.info.version = localItem.version
}
res.data.info.enable = localItem.state === moduleInstallState.DISABLE ? false : true
} else {
res.data.info.state = 0
res.data.info.type = 'online'
}
state.goodsInfo = res.data.info
})
.catch((err) => {
if (loginExpired(err)) {
state.dialog.goodsInfo = false
}
})
.finally(() => {
state.loading.goodsInfo = false
})
}
/**
* 支付订单
* @param renew 是否是续费订单
*/
export const onBuy = (renew = false) => {
state.dialog.buy = true
state.loading.buy = true
createOrder({
goods_id: state.goodsInfo.id,
})
.then((res) => {
state.loading.buy = false
state.buy.renew = renew
state.buy.info = res.data.info
})
.catch((err) => {
state.dialog.buy = false
state.loading.buy = false
loginExpired(err)
})
}
export const onPay = (payType: 'score' | 'wx' | 'balance' | 'zfb') => {
state.common.payType = payType
state.loading.common = true
payOrder(state.buy.info.id, payType)
.then((res) => {
// 关闭其他弹窗
state.dialog.buy = false
state.dialog.goodsInfo = false
if (payType == 'wx' || payType == 'zfb') {
// 显示支付二维码
state.dialog.pay = true
state.payInfo = res.data
// 轮询获取支付状态
const timer = setInterval(() => {
payCheck(state.payInfo.info.sn)
.then(() => {
state.payInfo.pay.status = 'success'
clearInterval(timer)
if (state.buy.renew) {
showInfo(res.data.info.uid)
} else {
onPreInstallModule(res.data.info.uid, res.data.info.id, true)
}
state.dialog.pay = false
})
.catch(() => {})
}, 3000)
} else {
if (state.buy.renew) {
showInfo(res.data.info.uid)
} else {
onPreInstallModule(res.data.info.uid, res.data.info.id, true)
}
}
})
.catch((err) => {
loginExpired(err)
})
.finally(() => {
state.loading.common = false
})
}
export const showCommonLoading = (loadingTitle: moduleState['common']['loadingTitle']) => {
state.common.type = 'loading'
state.common.loadingTitle = loadingTitle
state.common.loadingComponentKey = uuid()
}
/**
* 模块预安装
*/
export const onPreInstallModule = (uid: string, id: number, needGetInstallableVersion: boolean, update: boolean = false) => {
state.dialog.common = true
showCommonLoading('init')
state.common.dialogTitle = i18n.global.t('module.Install')
const nextStep = (moduleState: number) => {
if (needGetInstallableVersion) {
// 获取模块版本列表
showCommonLoading('getInstallableVersion')
preDownload({
uid,
orderId: id,
sysVersion: state.sysVersion,
nuxtVersion: state.nuxtVersion,
installed: state.installedModuleUids,
})
.then((res) => {
state.common.uid = uid
state.common.update = update
state.common.type = 'selectVersion'
state.common.dialogTitle = i18n.global.t('module.Select Version')
state.common.versions = res.data.versions
// 关闭其他弹窗
state.dialog.baAccount = false
state.dialog.buy = false
state.dialog.goodsInfo = false
})
.catch((res) => {
if (loginExpired(res)) return
state.dialog.common = false
})
} else {
// 立即安装(上传安装、继续安装)
showCommonLoading(moduleState === moduleInstallState.UNINSTALLED ? 'download' : 'install')
execInstall(uid, id, '', update)
// 关闭其他弹窗
state.dialog.baAccount = false
state.dialog.buy = false
state.dialog.goodsInfo = false
}
}
if (update) {
nextStep(moduleInstallState.DISABLE)
} else {
// 获取安装状态
getInstallState(uid).then((res) => {
if (
res.data.state === moduleInstallState.INSTALLED ||
res.data.state === moduleInstallState.DISABLE ||
res.data.state === moduleInstallState.DIRECTORY_OCCUPIED
) {
ElNotification({
type: 'error',
message:
res.data.state === moduleInstallState.INSTALLED || res.data.state === moduleInstallState.DISABLE
? i18n.global.t('module.Installation cancelled because module already exists!')
: i18n.global.t('module.Installation cancelled because the directory required by the module is occupied!'),
})
state.dialog.common = false
return
}
nextStep(res.data.state)
})
}
}
/**
* 执行安装请求,还包含启用、安装时的冲突处理
*/
export const execInstall = (uid: string, id: number, version: string = '', update: boolean = false, extend: anyObj = {}) => {
postInstallModule(uid, id, version, update, extend)
.then(() => {
state.common.dialogTitle = i18n.global.t('module.Installation complete')
state.common.moduleState = moduleInstallState.INSTALLED
state.common.type = 'done'
onRefreshTableData()
})
.catch((res) => {
if (loginExpired(res)) return
if (res.code == -1) {
state.common.uid = res.data.uid
state.common.type = 'installConflict'
state.common.dialogTitle = i18n.global.t('module.A conflict is found Please handle it manually')
state.common.fileConflict = res.data.fileConflict
state.common.dependConflict = res.data.dependConflict
} else if (res.code == -2) {
state.common.type = 'done'
state.common.uid = res.data.uid
state.common.dialogTitle = i18n.global.t('module.Wait for dependent installation')
state.common.moduleState = moduleInstallState.DEPENDENT_WAIT_INSTALL
state.common.waitInstallDepend = res.data.wait_install
state.common.dependInstallState = 'executing'
const terminal = useTerminal()
if (res.data.wait_install.includes('npm_dependent_wait_install')) {
terminal.addTaskPM('web-install', true, 'module-install:' + res.data.uid, (res: number) => {
terminalTaskExecComplete(res, 'npm_dependent_wait_install')
})
}
if (res.data.wait_install.includes('nuxt_npm_dependent_wait_install')) {
terminal.addTaskPM('nuxt-install', true, 'module-install:' + res.data.uid, (res: number) => {
terminalTaskExecComplete(res, 'nuxt_npm_dependent_wait_install')
})
}
if (res.data.wait_install.includes('composer_dependent_wait_install')) {
terminal.addTask('composer.update', true, 'module-install:' + res.data.uid, (res: number) => {
terminalTaskExecComplete(res, 'composer_dependent_wait_install')
})
}
} else if (res.code == 0) {
ElNotification({
type: 'error',
message: res.msg,
zIndex: SYSTEM_ZINDEX,
})
state.dialog.common = false
onRefreshTableData()
}
})
.finally(() => {
state.loading.common = false
})
}
const terminalTaskExecComplete = (res: number, type: string) => {
if (res == taskStatus.Success) {
state.common.waitInstallDepend = state.common.waitInstallDepend.filter((depend: string) => {
return depend != type
})
if (state.common.waitInstallDepend.length == 0) {
state.common.dependInstallState = 'success'
// 仅在命令全部执行完毕才刷新数据
if (router.currentRoute.value.name === 'moduleStore/moduleStore') {
onRefreshTableData()
}
}
} else {
const terminal = useTerminal()
terminal.toggle(true)
state.common.dependInstallState = 'fail'
// 有命令执行失败了,刷新一次数据
if (router.currentRoute.value.name === 'moduleStore/moduleStore') {
onRefreshTableData()
}
}
// 连续安装模块的情况中,首个模块的命令执行完毕时,自动启动了热更新
if (router.currentRoute.value.name === 'moduleStore/moduleStore') {
closeHotUpdate('modules')
}
}
export const onDisable = (confirmConflict = false) => {
state.loading.common = true
// 拼装依赖处理方案
if (confirmConflict) {
const dependConflict: anyObj = {}
for (const key in state.common.disableDependConflict) {
if (state.common.disableDependConflict[key]['solution'] != 'delete') {
continue
}
if (typeof dependConflict[state.common.disableDependConflict[key].env] == 'undefined') {
dependConflict[state.common.disableDependConflict[key].env] = []
}
dependConflict[state.common.disableDependConflict[key].env].push(state.common.disableDependConflict[key].depend)
}
state.common.disableParams['confirmConflict'] = 1
state.common.disableParams['dependConflictSolution'] = dependConflict
}
changeState(state.common.disableParams)
.then(() => {
ElNotification({
type: 'success',
message: i18n.global.t('module.The operation succeeds Please clear the system cache and refresh the browser ~'),
zIndex: SYSTEM_ZINDEX,
})
state.dialog.common = false
onRefreshTableData()
})
.catch((res) => {
if (res.code == -1) {
state.dialog.common = true
state.common.dialogTitle = i18n.global.t('module.Deal with conflict')
state.common.type = 'disableConfirmConflict'
state.common.disableDependConflict = res.data.dependConflict
if (res.data.conflictFile && res.data.conflictFile.length) {
const conflictFile = []
for (const key in res.data.conflictFile) {
conflictFile.push({
file: res.data.conflictFile[key],
})
}
state.common.disableConflictFile = conflictFile
}
} else if (res.code == -2) {
state.dialog.common = true
const commandsData = {
type: 'disable',
commands: res.data.wait_install,
}
state.common.uid = state.goodsInfo.uid
execCommand(commandsData)
} else if (res.code == -3) {
// 更新
onPreInstallModule(state.goodsInfo.uid, state.goodsInfo.purchased, true, true)
} else {
ElNotification({
type: 'error',
message: res.msg,
zIndex: SYSTEM_ZINDEX,
})
if (state.common.disableParams && state.common.disableParams.uid) {
showInfo(state.common.disableParams.uid)
} else {
onRefreshTableData()
}
}
})
.finally(() => {
state.loading.common = false
})
}
export const onEnable = (uid: string) => {
state.loading.common = true
changeState({
uid: uid,
state: 1,
})
.then(() => {
state.dialog.common = true
showCommonLoading('init')
state.common.dialogTitle = i18n.global.t('Enable')
execInstall(uid, 0)
state.dialog.goodsInfo = false
})
.catch((res) => {
ElNotification({
type: 'error',
message: res.msg,
zIndex: SYSTEM_ZINDEX,
})
state.loading.common = false
})
}
export const loginExpired = (res: ApiResponse) => {
const baAccount = useBaAccount()
if (res.code == 301 || res.code == 408) {
baAccount.removeToken()
state.dialog.baAccount = true
return true
}
return false
}
const modulesOnlyLocalHandle = (modules: anyObj) => {
if (!state.table.onlyLocal) return modules
return modules.filter((item: anyObj) => {
return item.installed
})
}
export const execCommand = (data: anyObj) => {
if (data.type == 'disable') {
state.dialog.common = true
state.common.type = 'done'
state.common.dialogTitle = i18n.global.t('module.Wait for dependent installation')
state.common.moduleState = moduleInstallState.DISABLE
state.common.dependInstallState = 'executing'
const terminal = useTerminal()
data.commands.forEach((item: anyObj) => {
state.common.waitInstallDepend.push(item.type)
if (item.pm) {
if (item.command == 'web-install') {
changeListenDirtyFileSwitch(false)
}
terminal.addTaskPM(item.command, true, '', (res: number) => {
terminalTaskExecComplete(res, item.type)
if (item.command == 'web-install') {
changeListenDirtyFileSwitch(true)
}
})
} else {
terminal.addTask(item.command, true, '', (res: number) => {
terminalTaskExecComplete(res, item.type)
})
}
})
}
}
export const specificUserName = (userInfo: Partial<UserInfo>) => {
return userInfo.nickname + '' + (userInfo.email || userInfo.mobile || 'ID:' + userInfo.id) + ''
}
export const currency = (price: number, val: number) => {
if (typeof price == 'undefined' || typeof val == 'undefined') {
return '-'
}
if (val == 0) {
return parseInt(price.toString()) + i18n.global.t('Integral')
} else {
return '¥' + price
}
}
export const moduleStatus = (state: number) => {
switch (state) {
case moduleInstallState.INSTALLED:
return {
type: '',
text: i18n.global.t('module.installed'),
}
case moduleInstallState.WAIT_INSTALL:
return {
type: 'success',
text: i18n.global.t('module.Wait for installation'),
}
case moduleInstallState.CONFLICT_PENDING:
return {
type: 'danger',
text: i18n.global.t('module.Conflict pending'),
}
case moduleInstallState.DEPENDENT_WAIT_INSTALL:
return {
type: 'warning',
text: i18n.global.t('module.Dependency to be installed'),
}
case moduleInstallState.DISABLE:
return {
type: 'warning',
text: i18n.global.t('Disable'),
}
default:
return {
type: 'info',
text: i18n.global.t('Unknown'),
}
}
}