611 lines
26 KiB
Vue
611 lines
26 KiB
Vue
<template>
|
||
<div>
|
||
<el-dialog v-model="state.dialog.goodsInfo" class="goods-info-dialog" :title="t('module.detailed information')" width="60%">
|
||
<el-scrollbar v-loading="state.loading.goodsInfo" :key="state.goodsInfo.uid" :height="500">
|
||
<div class="goods-info">
|
||
<div class="goods-images">
|
||
<el-carousel height="300" v-if="state.goodsInfo.images" indicator-position="outside">
|
||
<el-carousel-item class="goods-image-item" v-for="(image, idx) in state.goodsInfo.images" :key="idx">
|
||
<el-image fit="contain" :preview-src-list="state.goodsInfo.images" :preview-teleported="true" :src="image"></el-image>
|
||
</el-carousel-item>
|
||
</el-carousel>
|
||
</div>
|
||
<div class="goods-basic">
|
||
<h4 class="goods-basic-title">{{ state.goodsInfo.title }}</h4>
|
||
<div class="goods-tag">
|
||
<el-tag v-for="(tag, idx) in state.goodsInfo.tags" :key="idx" :type="tag.type ? tag.type : 'primary'">
|
||
{{ tag.name }}
|
||
</el-tag>
|
||
</div>
|
||
<div class="basic-item">
|
||
<div class="basic-item-title">{{ t('module.Price') }}</div>
|
||
<div class="basic-item-price">
|
||
{{
|
||
typeof state.goodsInfo.currency_select != 'undefined'
|
||
? currency(state.goodsInfo.present_price, state.goodsInfo.currency_select)
|
||
: '-'
|
||
}}
|
||
</div>
|
||
</div>
|
||
<div class="basic-item">
|
||
<div class="basic-item-title">{{ t('module.Last updated') }}</div>
|
||
<div class="basic-item-content">{{ state.goodsInfo.updatetime ? timeFormat(state.goodsInfo.updatetime) : '-' }}</div>
|
||
</div>
|
||
<div class="basic-item">
|
||
<div class="basic-item-title">{{ t('module.Published on') }}</div>
|
||
<div class="basic-item-content">{{ state.goodsInfo.createtime ? timeFormat(state.goodsInfo.createtime) : '-' }}</div>
|
||
</div>
|
||
<div v-if="!installButtonState.stateSwitch.includes(state.goodsInfo.state)" class="basic-item">
|
||
<div class="basic-item-title">{{ t('module.amount of downloads') }}</div>
|
||
<div class="basic-item-content">{{ state.goodsInfo.downloads ? state.goodsInfo.downloads : '-' }}</div>
|
||
</div>
|
||
<div class="basic-item">
|
||
<div class="basic-item-title">{{ t('module.Module classification') }}</div>
|
||
<div class="basic-item-content">{{ state.goodsInfo.category ? state.goodsInfo.category.name : '-' }}</div>
|
||
</div>
|
||
<div class="basic-item">
|
||
<div class="basic-item-title">{{ t('module.Module documentation') }}</div>
|
||
<div class="basic-item-content">
|
||
<el-link
|
||
type="primary"
|
||
class="basic-item-link"
|
||
v-if="state.goodsInfo.docs"
|
||
target="_blank"
|
||
:href="`https://doc.buildadmin.com/md/${state.goodsInfo.docs.name ? state.goodsInfo.docs.name : state.goodsInfo.docs.id}`"
|
||
rel="noopener noreferrer"
|
||
>
|
||
{{ t('module.Click to access') }}
|
||
</el-link>
|
||
<span v-else>-</span>
|
||
</div>
|
||
</div>
|
||
<div class="basic-item">
|
||
<div class="basic-item-title">{{ t('module.Developer Homepage') }}</div>
|
||
<div class="basic-item-content">
|
||
<el-link
|
||
type="primary"
|
||
class="basic-item-link"
|
||
v-if="state.goodsInfo.author_url"
|
||
target="_blank"
|
||
:href="state.goodsInfo.author_url"
|
||
rel="noopener noreferrer"
|
||
>
|
||
{{ t('module.Click to access') }}
|
||
</el-link>
|
||
<span v-else>-</span>
|
||
</div>
|
||
</div>
|
||
<div v-if="installButtonState.stateSwitch.includes(state.goodsInfo.state)" class="basic-item">
|
||
<div class="basic-item-title">{{ t('module.Module status') }}</div>
|
||
<div class="basic-item-content">
|
||
<el-switch
|
||
@change="onChangeState"
|
||
:loading="state.loading.common"
|
||
:disabled="state.loading.common"
|
||
v-model="state.goodsInfo.enable"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="basic-buttons">
|
||
<el-dropdown
|
||
v-if="
|
||
(!state.goodsInfo.purchased || installButtonState.InstallNow.includes(state.goodsInfo.state)) &&
|
||
state.goodsInfo.demo &&
|
||
state.goodsInfo.demo.length > 0
|
||
"
|
||
>
|
||
<el-button class="basic-button-demo" type="primary">
|
||
<span class="basic-button-dropdown-span">{{ t('module.View demo') }}</span>
|
||
<Icon color="#ffffff" size="16" name="el-icon-ArrowDown" />
|
||
</el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item
|
||
v-for="(demo, idx) in state.goodsInfo.demo"
|
||
:key="idx"
|
||
@click="openDemo(demo.link, demo.image ? false : true)"
|
||
class="basic-button-dropdown-item"
|
||
>
|
||
<el-popover
|
||
placement="right"
|
||
:title="t('module.Code scanning Preview')"
|
||
trigger="hover"
|
||
:disabled="demo.image ? false : true"
|
||
:width="174"
|
||
>
|
||
<template #reference>
|
||
<div class="demo-item-title">
|
||
<Icon :name="demo.icon" size="14" color="var(--el-color-primary)" />{{ demo.title }}
|
||
</div>
|
||
</template>
|
||
<div class="demo-image">
|
||
<img :src="demo.image" alt="" />
|
||
</div>
|
||
</el-popover>
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
<el-button
|
||
v-if="
|
||
!state.goodsInfo.purchased &&
|
||
installButtonState.buy.includes(state.goodsInfo.state) &&
|
||
state.goodsInfo.type == 'online'
|
||
"
|
||
@click="onBuy(false)"
|
||
v-blur
|
||
class="basic-button-item"
|
||
type="danger"
|
||
>
|
||
{{ t('module.Buy now') }}
|
||
</el-button>
|
||
<el-button
|
||
v-if="
|
||
(state.goodsInfo.state == moduleInstallState.UNINSTALLED && state.goodsInfo.purchased) ||
|
||
state.goodsInfo.state == moduleInstallState.WAIT_INSTALL
|
||
"
|
||
@click="
|
||
onPreInstallModule(
|
||
state.goodsInfo.uid,
|
||
state.goodsInfo.purchased,
|
||
state.goodsInfo.state == moduleInstallState.WAIT_INSTALL ? false : true
|
||
)
|
||
"
|
||
:loading="state.loading.common"
|
||
v-blur
|
||
class="basic-button-item"
|
||
type="success"
|
||
>
|
||
{{ t('module.Install now') }}
|
||
</el-button>
|
||
<el-button
|
||
v-if="installButtonState.continueInstallation.includes(state.goodsInfo.state)"
|
||
@click="onPreInstallModule(state.goodsInfo.uid, state.goodsInfo.purchased, false)"
|
||
:loading="state.loading.common"
|
||
v-blur
|
||
class="basic-button-item"
|
||
type="success"
|
||
>
|
||
{{ t('module.continue installation') }}
|
||
</el-button>
|
||
<el-button
|
||
v-if="installButtonState.alreadyInstalled.includes(state.goodsInfo.state)"
|
||
v-blur
|
||
:disabled="true"
|
||
class="basic-button-item"
|
||
>
|
||
{{ t('module.installed') }} v{{ state.goodsInfo.version }}
|
||
</el-button>
|
||
<el-button
|
||
v-if="state.goodsInfo.type == 'local' && !installButtonState.alreadyInstalled.includes(state.goodsInfo.state)"
|
||
v-blur
|
||
:disabled="true"
|
||
class="basic-button-item"
|
||
>
|
||
{{ t('module.Local module') }} v{{ state.goodsInfo.version }}
|
||
</el-button>
|
||
<el-button
|
||
v-if="state.goodsInfo.new_version && installButtonState.updateButton.includes(state.goodsInfo.state)"
|
||
@click="onUpdate(state.goodsInfo.uid, state.goodsInfo.purchased)"
|
||
v-loading="state.loading.common"
|
||
v-blur
|
||
class="basic-button-item"
|
||
type="success"
|
||
>
|
||
{{ t('module.to update') }}
|
||
</el-button>
|
||
<el-button
|
||
v-if="installButtonState.stateSwitch.includes(state.goodsInfo.state)"
|
||
v-loading="state.loading.common"
|
||
@click="unInstall(state.goodsInfo.uid)"
|
||
v-blur
|
||
class="basic-button-item"
|
||
type="danger"
|
||
>
|
||
{{ t('module.uninstall') }}
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
<div v-if="!isEmpty(state.goodsInfo.developer)" class="goods-developer">
|
||
<div class="developer-header">
|
||
<el-avatar :size="60" :src="state.goodsInfo.developer.avatar" />
|
||
<div class="developer-name">
|
||
<h3 class="developer-nickname">{{ state.goodsInfo.developer.nickname }}</h3>
|
||
<div class="developer-group">
|
||
{{ state.goodsInfo.developer.group ? state.goodsInfo.developer.group : '-' }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-if="state.goodsInfo.qq" class="developer-contact">
|
||
<h4 class="developer-info-title">{{ t('module.Contact developer') }}</h4>
|
||
<div class="contact-item">
|
||
<a
|
||
rel="noopener noreferrer"
|
||
target="_blank"
|
||
:href="'http://wpa.qq.com/msgrd?v=3&uin=' + state.goodsInfo.qq + '&site=qq&menu=yes'"
|
||
>
|
||
<span>QQ:{{ state.goodsInfo.qq }}</span>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<div class="developer-recommend">
|
||
<h4 class="developer-info-title">{{ t('module.Other works of developers') }}</h4>
|
||
<div v-if="state.goodsInfo.developer.goods.length > 0" class="recommend-goods">
|
||
<div
|
||
v-for="(goods_item, idx) in state.goodsInfo.developer.goods"
|
||
:key="idx"
|
||
@click="showInfo(goods_item.uid)"
|
||
class="recommend-goods-item"
|
||
>
|
||
<el-image fit="contain" class="recommend-goods-logo" :src="goods_item.logo"> </el-image>
|
||
<div class="recommend-goods-title">{{ goods_item.title }}</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="data-empty">{{ t('module.There are no more works') }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="goods-detail ba-markdown" v-html="state.goodsInfo.detail_editor"></div>
|
||
<div class="goods-version">
|
||
<h1>{{ t('module.Update Log') }}</h1>
|
||
<div class="version-timeline" v-if="state.goodsInfo.version_log">
|
||
<el-timeline>
|
||
<el-timeline-item
|
||
v-for="(version, idx) in state.goodsInfo.version_log"
|
||
:key="idx"
|
||
:timestamp="timeFormat(version.createtime)"
|
||
placement="top"
|
||
:color="idx == 0 ? 'var(--el-color-success)' : ''"
|
||
>
|
||
<el-card class="version-card" shadow="hover">
|
||
<template #header>
|
||
<div class="version-card-header">
|
||
<h2>{{ version.title }}</h2>
|
||
<span class="version-short-describe">{{ version.short_describe }}</span>
|
||
</div>
|
||
</template>
|
||
<div
|
||
class="version-detail ba-markdown"
|
||
v-html="version.describe ? version.describe : t('module.No detailed update log')"
|
||
></div>
|
||
</el-card>
|
||
</el-timeline-item>
|
||
</el-timeline>
|
||
</div>
|
||
<div v-else class="empty-update-log">{{ $t('module.No detailed update log') }}</div>
|
||
</div>
|
||
</el-scrollbar>
|
||
</el-dialog>
|
||
<Buy />
|
||
<Pay />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ElMessageBox } from 'element-plus'
|
||
import { isEmpty } from 'lodash-es'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { currency, onBuy, onDisable, onEnable, onPreInstallModule, onRefreshTableData, showInfo } from '../index'
|
||
import { state } from '../store'
|
||
import { moduleInstallState } from '../types'
|
||
import Buy from './buy.vue'
|
||
import Pay from './pay.vue'
|
||
import { getInstallState, postUninstall } from '/@/api/backend/module'
|
||
import { useBaAccount } from '/@/stores/baAccount'
|
||
import { timeFormat } from '/@/utils/common'
|
||
|
||
const installButtonState = {
|
||
InstallNow: [moduleInstallState.UNINSTALLED, moduleInstallState.WAIT_INSTALL],
|
||
continueInstallation: [moduleInstallState.CONFLICT_PENDING, moduleInstallState.DEPENDENT_WAIT_INSTALL],
|
||
alreadyInstalled: [moduleInstallState.INSTALLED],
|
||
stateSwitch: [
|
||
moduleInstallState.INSTALLED,
|
||
moduleInstallState.CONFLICT_PENDING,
|
||
moduleInstallState.DEPENDENT_WAIT_INSTALL,
|
||
moduleInstallState.DISABLE,
|
||
],
|
||
updateButton: [moduleInstallState.WAIT_INSTALL, moduleInstallState.INSTALLED, moduleInstallState.DISABLE],
|
||
buy: [moduleInstallState.UNINSTALLED],
|
||
}
|
||
|
||
const { t } = useI18n()
|
||
const openDemo = (url: string, open: boolean) => {
|
||
if (!open || !url) return
|
||
window.open(url)
|
||
}
|
||
|
||
const onChangeState = () => {
|
||
if (state.goodsInfo.enable) {
|
||
onEnable(state.goodsInfo.uid)
|
||
} else {
|
||
state.common.disableParams = {
|
||
uid: state.goodsInfo.uid,
|
||
state: 0,
|
||
}
|
||
onDisable()
|
||
}
|
||
}
|
||
|
||
const unInstall = (uid: string) => {
|
||
state.loading.common = true
|
||
postUninstall(uid)
|
||
.then(() => {
|
||
onRefreshTableData()
|
||
state.dialog.goodsInfo = false
|
||
})
|
||
.finally(() => {
|
||
state.loading.common = false
|
||
})
|
||
}
|
||
|
||
const onUpdate = (uid: string, order: number) => {
|
||
// 无有效订单
|
||
if (!order) {
|
||
ElMessageBox.confirm(t('module.No module purchase order was found'), t('Reminder'), {
|
||
confirmButtonText: t('Confirm'),
|
||
cancelButtonText: t('Cancel'),
|
||
type: 'warning',
|
||
})
|
||
.then(() => {
|
||
onBuy(true)
|
||
})
|
||
.catch(() => {})
|
||
return
|
||
}
|
||
|
||
// 未登录
|
||
const baAccount = useBaAccount()
|
||
if (!baAccount.token) {
|
||
state.dialog.baAccount = true
|
||
return
|
||
}
|
||
state.loading.common = true
|
||
getInstallState(uid)
|
||
.then((res) => {
|
||
if (res.data.state == moduleInstallState.DISABLE) {
|
||
onPreInstallModule(uid, order, true, true)
|
||
} else {
|
||
ElMessageBox.confirm(t('module.You need to disable this module before updating Do you want to disable it now?'), t('Reminder'), {
|
||
confirmButtonText: t('module.Disable and update'),
|
||
cancelButtonText: t('Cancel'),
|
||
type: 'warning',
|
||
})
|
||
.then(() => {
|
||
state.common.disableParams = {
|
||
uid: uid,
|
||
state: 0,
|
||
update: 1,
|
||
}
|
||
onDisable()
|
||
})
|
||
.catch(() => {})
|
||
}
|
||
})
|
||
.finally(() => {
|
||
state.loading.common = false
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
:deep(.goods-info-dialog) .el-dialog__body {
|
||
padding: 0px 20px;
|
||
}
|
||
.demo-image,
|
||
.demo-image img {
|
||
width: 150px;
|
||
height: 150px;
|
||
}
|
||
.demo-item-title {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.icon {
|
||
margin-right: 6px;
|
||
}
|
||
}
|
||
.goods-info {
|
||
display: flex;
|
||
position: relative;
|
||
.goods-images {
|
||
max-width: 41%;
|
||
width: 300px;
|
||
.goods-image-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
:deep(.el-carousel__indicators) {
|
||
line-height: 10px;
|
||
.el-carousel__indicator {
|
||
padding: 0 var(--el-carousel-indicator-padding-horizontal);
|
||
}
|
||
}
|
||
}
|
||
.goods-basic {
|
||
position: relative;
|
||
|
||
.goods-basic-title {
|
||
padding-bottom: 20px;
|
||
}
|
||
flex: 1;
|
||
padding: 0 10px;
|
||
.basic-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 4px 0;
|
||
.basic-item-title {
|
||
font-size: var(--el-font-size-base);
|
||
color: var(--el-text-color-secondary);
|
||
width: 80px;
|
||
}
|
||
.basic-item-price {
|
||
font-size: 16px;
|
||
color: var(--el-color-danger);
|
||
}
|
||
.basic-item-content {
|
||
font-size: var(--el-font-size-base);
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
}
|
||
.basic-button-dropdown-span {
|
||
padding-right: 6px;
|
||
}
|
||
.basic-buttons {
|
||
padding-top: 6px;
|
||
}
|
||
.basic-button-demo {
|
||
margin-right: 10px;
|
||
}
|
||
}
|
||
.goods-developer {
|
||
width: 20%;
|
||
border-left: 1px solid var(--ba-bg-color);
|
||
padding: 10px;
|
||
position: absolute;
|
||
right: 0;
|
||
.developer-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
.developer-name {
|
||
padding-left: 10px;
|
||
flex: 1;
|
||
.developer-group {
|
||
padding-top: 5px;
|
||
font-size: var(--el-font-size-extra-small);
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
}
|
||
}
|
||
.developer-info-title {
|
||
color: var(--el-text-color-secondary);
|
||
padding-top: 15px;
|
||
line-height: 20px;
|
||
text-align: center;
|
||
}
|
||
.contact-item {
|
||
cursor: pointer;
|
||
padding-left: 10px;
|
||
line-height: 30px;
|
||
text-align: center;
|
||
a {
|
||
color: var(--el-color-primary);
|
||
text-decoration: none;
|
||
}
|
||
}
|
||
.recommend-goods-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin: 4px 0;
|
||
cursor: pointer;
|
||
padding: 6px;
|
||
&:hover {
|
||
background-color: var(--ba-bg-color);
|
||
}
|
||
.recommend-goods-logo {
|
||
width: 42px;
|
||
border-radius: var(--el-border-radius-base);
|
||
}
|
||
.recommend-goods-title {
|
||
flex: 1;
|
||
margin-left: 6px;
|
||
font-size: var(--el-font-size-small);
|
||
display: block;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
display: -webkit-box;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 2;
|
||
line-clamp: 2;
|
||
line-height: 15px;
|
||
height: 28px;
|
||
}
|
||
}
|
||
.developer-recommend {
|
||
.data-empty {
|
||
font-size: var(--el-font-size-extra-small);
|
||
color: var(--el-text-color-secondary);
|
||
text-align: center;
|
||
padding: 6px;
|
||
}
|
||
}
|
||
}
|
||
.el-carousel__item:nth-child(2n) {
|
||
background-color: #99a9bf;
|
||
}
|
||
.basic-item-link {
|
||
font-size: var(--el-font-size-small);
|
||
}
|
||
}
|
||
.basic-button-item {
|
||
--el-loading-spinner-size: 22px;
|
||
}
|
||
.goods-detail {
|
||
width: 80%;
|
||
}
|
||
.goods-version {
|
||
width: 80%;
|
||
h1 {
|
||
margin: 1.4em 0 0.8em;
|
||
font-weight: 700;
|
||
font-size: var(--el-font-size-large);
|
||
text-transform: uppercase;
|
||
color: var(--el-color-primary);
|
||
}
|
||
.version-timeline {
|
||
padding-left: 2px;
|
||
:deep(.el-card__body) {
|
||
padding: 10px 20px 20px 20px;
|
||
}
|
||
}
|
||
.version-card {
|
||
border: 1px solid var(--el-color-info-light-9);
|
||
}
|
||
.version-card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
}
|
||
.empty-update-log {
|
||
display: flex;
|
||
justify-content: center;
|
||
color: var(--el-color-info);
|
||
}
|
||
/* 商品详情弹窗-s */
|
||
@media screen and (max-width: 1440px) {
|
||
:deep(.goods-info-dialog) {
|
||
--el-dialog-width: 65% !important;
|
||
}
|
||
}
|
||
@media screen and (max-width: 1280px) {
|
||
:deep(.goods-info-dialog) {
|
||
--el-dialog-width: 80% !important;
|
||
}
|
||
}
|
||
@media screen and (max-width: 1024px) {
|
||
:deep(.goods-info-dialog) {
|
||
--el-dialog-width: 92% !important;
|
||
}
|
||
}
|
||
/* 商品详情弹窗-e */
|
||
@media screen and (max-width: 865px) {
|
||
.goods-info .goods-developer {
|
||
display: none;
|
||
}
|
||
}
|
||
@media screen and (max-width: 540px) {
|
||
.goods-info {
|
||
flex-wrap: wrap;
|
||
.goods-images {
|
||
max-width: 100%;
|
||
width: 100%;
|
||
}
|
||
}
|
||
.goods-detail {
|
||
padding-top: 15px;
|
||
}
|
||
}
|
||
</style>
|