feat: add dashboard and virtual pay enhancements
This commit is contained in:
9
ruoyi-ui/src/api/app/dashboard.js
Normal file
9
ruoyi-ui/src/api/app/dashboard.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询首页业务指标
|
||||
export function getDashboardSummary() {
|
||||
return request({
|
||||
url: '/app/dashboard/summary',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -76,9 +76,9 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="付费价格" align="center" prop="priceFen">
|
||||
<el-table-column label="付费起价" align="center" prop="priceFen">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.isAd == 3">¥{{ (scope.row.priceFen / 100).toFixed(2) }}</span>
|
||||
<span v-if="scope.row.isAd == 3">¥{{ formatPrice(scope.row.priceFen) }} 起</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -100,7 +100,7 @@
|
||||
@pagination="getList" />
|
||||
|
||||
<!-- 添加或修改资源对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="50%" append-to-body>
|
||||
<el-dialog :title="title" :visible.sync="open" width="72%" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="资源标题" prop="resourceTitle">
|
||||
<el-input v-model="form.resourceTitle" placeholder="请输入资源标题" />
|
||||
@@ -140,10 +140,6 @@
|
||||
<el-form-item v-if="form.isAd == 2" label="兑换积分" prop="adNumber">
|
||||
<el-input v-model="form.adNumber" placeholder="请输入需要兑换多少积分解锁" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.isAd == 3" label="价格(分)" prop="priceFen">
|
||||
<el-input-number v-model="form.priceFen" :min="1" :step="100" controls-position="right" />
|
||||
<span class="form-tip">系统将按价格自动匹配已配置的微信道具</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="权重" prop="weight">
|
||||
<el-input v-model="form.weight" placeholder="请输入权重" />
|
||||
</el-form-item>
|
||||
@@ -151,7 +147,15 @@
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
<el-divider content-position="center">资源列信息</el-divider>
|
||||
<el-divider content-position="center">{{ form.isAd == 3 ? '资源规格' : '资源列信息' }}</el-divider>
|
||||
<el-alert
|
||||
v-if="form.isAd == 3"
|
||||
title="排序代表版本等级:高排序包含低排序,价格必须递增;每个原价和可能产生的升级差价都要配置微信道具档位。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb8"
|
||||
/>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAddAppResourceList">添加</el-button>
|
||||
@@ -165,19 +169,50 @@
|
||||
@selection-change="handleAppResourceListSelectionChange" ref="appResourceList">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="序号" align="center" prop="index" width="50" />
|
||||
<el-table-column label="资源名称" prop="listName">
|
||||
<el-table-column :label="form.isAd == 3 ? '规格名称' : '资源名称'" prop="listName" min-width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.listName" placeholder="请输入资源名称" />
|
||||
<el-input v-model="scope.row.listName" :placeholder="form.isAd == 3 ? '如:源码版' : '请输入资源名称'" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="资源地址" prop="listUrl">
|
||||
<el-table-column v-if="form.isAd == 3" label="价格(分)" prop="priceFen" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.listUrl" placeholder="请输入资源地址" />
|
||||
<el-input-number
|
||||
v-model="scope.row.priceFen"
|
||||
:min="1"
|
||||
:step="100"
|
||||
controls-position="right"
|
||||
style="width: 130px"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="访问密码" prop="password">
|
||||
<el-table-column label="资源地址" prop="listUrl" min-width="220">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.password" placeholder="请输入访问密码" />
|
||||
<el-input
|
||||
v-model="scope.row.listUrl"
|
||||
:name="`resource-url-${scope.$index}`"
|
||||
autocomplete="off"
|
||||
placeholder="请输入资源地址"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="访问密码" prop="password" width="130">
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="scope.row.password"
|
||||
:name="`resource-access-code-${scope.$index}`"
|
||||
autocomplete="off"
|
||||
placeholder="请输入访问密码"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="form.isAd == 3" label="排序" prop="sortOrder" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-input-number v-model="scope.row.sortOrder" :min="0" controls-position="right" style="width: 80px" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="form.isAd == 3" label="状态" prop="status" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-switch v-model="scope.row.status" :active-value="1" :inactive-value="0" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -267,11 +302,6 @@
|
||||
message: "需要广告不能为空",
|
||||
trigger: "change"
|
||||
}],
|
||||
priceFen: [{
|
||||
required: true,
|
||||
message: "付费价格不能为空",
|
||||
trigger: "blur"
|
||||
}],
|
||||
},
|
||||
directory:"appimg/fengmian/"
|
||||
};
|
||||
@@ -353,7 +383,12 @@
|
||||
const id = row.id || this.ids
|
||||
getResource(id).then(response => {
|
||||
this.form = response.data;
|
||||
this.appResourceListList = response.data.appResourceListList;
|
||||
this.appResourceListList = (response.data.appResourceListList || []).map((item, index) => ({
|
||||
...item,
|
||||
priceFen: item.priceFen || response.data.priceFen || null,
|
||||
status: item.status === 0 ? 0 : 1,
|
||||
sortOrder: item.sortOrder === null || item.sortOrder === undefined ? index : item.sortOrder
|
||||
}));
|
||||
this.open = true;
|
||||
this.title = "修改资源";
|
||||
});
|
||||
@@ -362,6 +397,9 @@
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
if (!this.validateResourceSpecs()) {
|
||||
return;
|
||||
}
|
||||
this.form.appResourceListList = this.appResourceListList;
|
||||
if (this.form.id != null) {
|
||||
updateResource(this.form).then(response => {
|
||||
@@ -402,6 +440,9 @@
|
||||
obj.listName = "";
|
||||
obj.listUrl = "";
|
||||
obj.password = "";
|
||||
obj.priceFen = this.form.isAd == 3 ? (this.form.priceFen || null) : 0;
|
||||
obj.status = 1;
|
||||
obj.sortOrder = this.appResourceListList.length;
|
||||
this.appResourceListList.push(obj);
|
||||
},
|
||||
/** 资源列删除按钮操作 */
|
||||
@@ -420,6 +461,60 @@
|
||||
handleAppResourceListSelectionChange(selection) {
|
||||
this.checkedAppResourceList = selection.map(item => item.index)
|
||||
},
|
||||
validateResourceSpecs() {
|
||||
const invalidPasswordItem = this.appResourceListList.find(item => {
|
||||
return item.password && /^https?:\/\//i.test(item.password.trim())
|
||||
})
|
||||
if (invalidPasswordItem) {
|
||||
this.$modal.msgError(`“${invalidPasswordItem.listName || '未命名资源'}”的访问密码不能填写网盘链接`)
|
||||
return false
|
||||
}
|
||||
if (this.form.isAd != 3) {
|
||||
return true
|
||||
}
|
||||
if (!this.appResourceListList.length) {
|
||||
this.$modal.msgError("付费资源至少需要配置一个规格")
|
||||
return false
|
||||
}
|
||||
for (let index = 0; index < this.appResourceListList.length; index++) {
|
||||
const item = this.appResourceListList[index]
|
||||
if (!item.listName || !item.listName.trim()) {
|
||||
this.$modal.msgError(`第${index + 1}个规格名称不能为空`)
|
||||
return false
|
||||
}
|
||||
if (!item.listUrl || !item.listUrl.trim()) {
|
||||
this.$modal.msgError(`规格“${item.listName}”的资源地址不能为空`)
|
||||
return false
|
||||
}
|
||||
if (!item.priceFen || item.priceFen <= 0) {
|
||||
this.$modal.msgError(`规格“${item.listName}”的价格必须大于0分`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
const orderedSpecs = [...this.appResourceListList].sort((left, right) => {
|
||||
return Number(left.sortOrder || 0) - Number(right.sortOrder || 0)
|
||||
})
|
||||
for (let index = 1; index < orderedSpecs.length; index++) {
|
||||
const previous = orderedSpecs[index - 1]
|
||||
const current = orderedSpecs[index]
|
||||
if (Number(current.sortOrder || 0) === Number(previous.sortOrder || 0)) {
|
||||
this.$modal.msgError("版本排序不能重复,请按等级从低到高填写")
|
||||
return false
|
||||
}
|
||||
if (Number(current.priceFen) <= Number(previous.priceFen)) {
|
||||
this.$modal.msgError("版本价格必须随排序等级递增")
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (!this.appResourceListList.some(item => item.status === 1)) {
|
||||
this.$modal.msgError("至少需要启用一个购买规格")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
formatPrice(priceFen) {
|
||||
return (Number(priceFen || 0) / 100).toFixed(2)
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('app/resource/export', {
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
<template slot-scope="scope">
|
||||
<div>{{ scope.row.resourceTitle || '-' }}</div>
|
||||
<div class="secondary-text">ID: {{ scope.row.resourceId }}</div>
|
||||
<div class="secondary-text">规格:{{ scope.row.specName || '旧版整项资源' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" align="right" width="100">
|
||||
@@ -144,6 +145,8 @@
|
||||
<el-descriptions-item label="OpenID">{{ detail.openId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="资源ID">{{ detail.resourceId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="资源标题">{{ detail.resourceTitle || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="规格ID">{{ detail.resourceListId || '旧版整项' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="购买规格">{{ detail.specName || '旧版整项资源' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="微信道具ID">{{ detail.productId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="微信内部订单号">{{ detail.wxOrderNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="微信交易号" :span="2">{{ detail.transactionId || '-' }}</el-descriptions-item>
|
||||
|
||||
@@ -1,7 +1,38 @@
|
||||
<template>
|
||||
<div class="app-container home">
|
||||
<el-row :gutter="20" class="dashboard-stats" v-loading="summaryLoading">
|
||||
<el-col
|
||||
v-for="item in metricCards"
|
||||
:key="item.key"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:lg="6"
|
||||
class="dashboard-stat-col"
|
||||
>
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-card__content">
|
||||
<div class="stat-card__icon" :class="`stat-card__icon--${item.theme}`">
|
||||
<i :class="item.icon"></i>
|
||||
</div>
|
||||
<div class="stat-card__body">
|
||||
<div class="stat-card__title">{{ item.title }}</div>
|
||||
<div class="stat-card__value">
|
||||
<span v-if="item.money">¥</span>{{ formatMetric(item.value, item.money) }}
|
||||
</div>
|
||||
<div class="stat-card__today">
|
||||
<span>今日新增</span>
|
||||
<span class="stat-card__today-value">
|
||||
+<span v-if="item.money">¥</span>{{ formatMetric(item.today, item.money) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :sm="24" :lg="24">
|
||||
<el-col :xs="24" :sm="24" :lg="12">
|
||||
<!-- 每日新增用户折线图 -->
|
||||
<el-card style="margin-bottom: 20px;">
|
||||
<div slot="header" class="clearfix">
|
||||
@@ -9,7 +40,9 @@
|
||||
</div>
|
||||
<div ref="userChart" style="height: 400px;"></div>
|
||||
</el-card>
|
||||
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="24" :sm="24" :lg="12">
|
||||
<!-- 每日新增支付金额折线图 -->
|
||||
<el-card style="margin-bottom: 20px;">
|
||||
<div slot="header" class="clearfix">
|
||||
@@ -958,6 +991,7 @@
|
||||
<script>
|
||||
import { getUserCountByDay } from '@/api/system/user';
|
||||
import { getDailyPayAmount } from '@/api/app/payStatistics';
|
||||
import { getDashboardSummary } from '@/api/app/dashboard';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
@@ -975,14 +1009,85 @@ export default {
|
||||
payStats: {
|
||||
dates: [],
|
||||
amounts: []
|
||||
},
|
||||
summaryLoading: false,
|
||||
dashboardSummary: {
|
||||
resourceTotal: 0,
|
||||
resourceToday: 0,
|
||||
userTotal: 0,
|
||||
userToday: 0,
|
||||
orderTotal: 0,
|
||||
orderToday: 0,
|
||||
amountTotal: 0,
|
||||
amountToday: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
metricCards() {
|
||||
return [
|
||||
{
|
||||
key: 'resource',
|
||||
title: '资源总数',
|
||||
value: this.dashboardSummary.resourceTotal,
|
||||
today: this.dashboardSummary.resourceToday,
|
||||
icon: 'el-icon-folder-opened',
|
||||
theme: 'blue',
|
||||
money: false
|
||||
},
|
||||
{
|
||||
key: 'user',
|
||||
title: '用户总数',
|
||||
value: this.dashboardSummary.userTotal,
|
||||
today: this.dashboardSummary.userToday,
|
||||
icon: 'el-icon-user',
|
||||
theme: 'green',
|
||||
money: false
|
||||
},
|
||||
{
|
||||
key: 'order',
|
||||
title: '订单总数',
|
||||
value: this.dashboardSummary.orderTotal,
|
||||
today: this.dashboardSummary.orderToday,
|
||||
icon: 'el-icon-shopping-cart-full',
|
||||
theme: 'orange',
|
||||
money: false
|
||||
},
|
||||
{
|
||||
key: 'amount',
|
||||
title: '订单总金额',
|
||||
value: this.dashboardSummary.amountTotal,
|
||||
today: this.dashboardSummary.amountToday,
|
||||
icon: 'el-icon-money',
|
||||
theme: 'purple',
|
||||
money: true
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadDashboardSummary();
|
||||
this.initUserChart();
|
||||
this.initPayChart();
|
||||
},
|
||||
methods: {
|
||||
loadDashboardSummary() {
|
||||
this.summaryLoading = true;
|
||||
getDashboardSummary().then(response => {
|
||||
this.dashboardSummary = Object.assign({}, this.dashboardSummary, response.data || {});
|
||||
}).finally(() => {
|
||||
this.summaryLoading = false;
|
||||
});
|
||||
},
|
||||
formatMetric(value, money) {
|
||||
const number = Number(value || 0);
|
||||
return number.toLocaleString('zh-CN', money ? {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
} : {
|
||||
maximumFractionDigits: 0
|
||||
});
|
||||
},
|
||||
goTarget(href) {
|
||||
window.open(href, "_blank");
|
||||
},
|
||||
@@ -1082,6 +1187,109 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dashboard-stats {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dashboard-stat-col {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 116px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: flex;
|
||||
flex: 0 0 64px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 18px;
|
||||
border-radius: 16px;
|
||||
font-size: 30px;
|
||||
|
||||
&--blue {
|
||||
color: #409eff;
|
||||
background: rgba(64, 158, 255, 0.12);
|
||||
}
|
||||
|
||||
&--green {
|
||||
color: #36b37e;
|
||||
background: rgba(54, 179, 126, 0.12);
|
||||
}
|
||||
|
||||
&--orange {
|
||||
color: #e6a23c;
|
||||
background: rgba(230, 162, 60, 0.14);
|
||||
}
|
||||
|
||||
&--purple {
|
||||
color: #8b5cf6;
|
||||
background: rgba(139, 92, 246, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__value {
|
||||
overflow: hidden;
|
||||
margin-top: 8px;
|
||||
color: #303133;
|
||||
font-size: 27px;
|
||||
font-weight: 600;
|
||||
line-height: 34px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__today {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 9px;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&__today-value {
|
||||
color: #36b37e;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1440px) {
|
||||
.stat-card {
|
||||
&__icon {
|
||||
flex-basis: 54px;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
margin-right: 12px;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 23px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home {
|
||||
blockquote {
|
||||
padding: 10px 20px;
|
||||
|
||||
@@ -17,34 +17,14 @@
|
||||
<!-- @keyup.enter.native="handleQuery"-->
|
||||
<!-- />-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item label="项目技术" prop="codeTechnology">
|
||||
<el-form-item label="其他技术" prop="codeTechnology">
|
||||
<el-input
|
||||
v-model="queryParams.codeTechnology"
|
||||
placeholder="请输入项目技术"
|
||||
placeholder="请输入其他技术"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目来源" prop="codeSource">
|
||||
<el-select v-model="queryParams.codeSource" placeholder="请选择项目来源" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.code_source"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="付费方式" prop="paymentType">
|
||||
<el-select v-model="queryParams.paymentType" placeholder="请选择付费方式" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.payment_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="网盘地址" prop="diskLink">-->
|
||||
<!-- <el-input-->
|
||||
<!-- v-model="queryParams.diskLink"-->
|
||||
@@ -124,17 +104,7 @@
|
||||
<el-table-column label="项目名称" align="center" prop="codeName"/>
|
||||
<!-- <el-table-column label="项目描述" align="center" prop="codeDesc" />-->
|
||||
<el-table-column label="运行环境" align="center" prop="codeEnvironment"/>
|
||||
<el-table-column label="项目技术" align="center" prop="codeTechnology"/>
|
||||
<el-table-column label="项目来源" align="center" prop="codeSource">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.code_source" :value="scope.row.codeSource"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="付费方式" align="center" prop="paymentType">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.payment_type" :value="scope.row.paymentType"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="其他技术" align="center" prop="codeTechnology"/>
|
||||
<!-- <el-table-column label="网盘地址" align="center" prop="diskLink" />-->
|
||||
<el-table-column label="是否发布" align="center" prop="publishFlag">
|
||||
<template slot-scope="scope">
|
||||
@@ -218,31 +188,17 @@
|
||||
<el-form-item label="运行环境" prop="codeEnvironment">
|
||||
<el-input v-model="form.codeEnvironment" placeholder="请输入运行环境"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目技术" prop="codeTechnology">
|
||||
<el-input v-model="form.codeTechnology" placeholder="请输入项目技术"/>
|
||||
<el-form-item label="前端" prop="frontendTechnology">
|
||||
<el-input v-model="form.frontendTechnology" placeholder="请输入前端技术"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目来源" prop="codeSource">
|
||||
<el-select v-model="form.codeSource" placeholder="请选择项目来源">
|
||||
<el-option
|
||||
v-for="dict in dict.type.code_source"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<el-form-item label="后端" prop="backendTechnology">
|
||||
<el-input v-model="form.backendTechnology" placeholder="请输入后端技术"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="付费方式" prop="paymentType">
|
||||
<el-radio-group v-model="form.paymentType">
|
||||
<el-radio
|
||||
v-for="dict in dict.type.payment_type"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{dict.label}}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<el-form-item label="数据库" prop="databaseTechnology">
|
||||
<el-input v-model="form.databaseTechnology" placeholder="请输入数据库技术"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="网盘地址" prop="diskLink">
|
||||
<el-input v-model="form.diskLink" placeholder="请输入网盘地址"/>
|
||||
<el-form-item label="其他技术" prop="codeTechnology">
|
||||
<el-input v-model="form.codeTechnology" placeholder="请输入其他技术"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否发布" prop="publishFlag">
|
||||
<el-radio-group v-model="form.publishFlag">
|
||||
@@ -254,12 +210,6 @@
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="截图文件" prop="pictureFile">
|
||||
<file-upload v-model="form.pictureFile"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="视频文件" prop="videoFile">
|
||||
<file-upload v-model="form.videoFile"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
@@ -275,27 +225,19 @@
|
||||
<span v-html="form.codeDesc"/>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运行环境" :span="4">{{ form.codeEnvironment }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目技术" :span="4">{{ form.codeTechnology }}</el-descriptions-item>
|
||||
<el-descriptions-item label="前端" :span="4">{{ form.frontendTechnology }}</el-descriptions-item>
|
||||
<el-descriptions-item label="后端" :span="4">{{ form.backendTechnology }}</el-descriptions-item>
|
||||
<el-descriptions-item label="数据库" :span="4">{{ form.databaseTechnology }}</el-descriptions-item>
|
||||
<el-descriptions-item label="其他技术" :span="4">{{ form.codeTechnology }}</el-descriptions-item>
|
||||
<!-- <el-descriptions-item label="项目来源">-->
|
||||
<!-- <dict-tag :options="dict.type.code_source" :value="form.codeSource"/>-->
|
||||
<!-- </el-descriptions-item>-->
|
||||
<!-- <el-descriptions-item label="付费方式">-->
|
||||
<!-- <dict-tag :options="dict.type.payment_type" :value="form.paymentType"/>-->
|
||||
<!-- </el-descriptions-item>-->
|
||||
<el-descriptions-item label="网盘地址" :span="2">{{ form.diskLink }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否发布" :span="2">
|
||||
<el-descriptions-item label="是否发布" :span="4">
|
||||
<dict-tag :options="dict.type.sys_yes_no" :value="form.publishFlag"/>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="截图文件" :span="4">
|
||||
<el-link v-if="form.pictureFile" :href="`${baseUrl}${form.pictureFile}`" :underline="false" target="_blank">
|
||||
<span class="el-icon-document"> 下载 </span>
|
||||
</el-link>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="视频文件" :span="4">
|
||||
<el-link v-if="form.videoFile" :href="`${baseUrl}${form.videoFile}`" :underline="false" target="_blank">
|
||||
<span class="el-icon-document"> 下载 </span>
|
||||
</el-link>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="截图展示" :span="4">
|
||||
<el-image
|
||||
style="width: 100px; height: 80px; padding: 3px;"
|
||||
@@ -373,6 +315,9 @@
|
||||
codeDesc: null,
|
||||
codeEnvironment: null,
|
||||
codeTechnology: null,
|
||||
frontendTechnology: null,
|
||||
backendTechnology: null,
|
||||
databaseTechnology: null,
|
||||
codeSource: null,
|
||||
paymentType: null,
|
||||
diskLink: null,
|
||||
@@ -430,6 +375,9 @@
|
||||
codeDesc: null,
|
||||
codeEnvironment: 'jdk1.8 + mysql5.7以上 + idea + vscode',
|
||||
codeTechnology: 'springboot + vue2 + elementui + nodejs14',
|
||||
frontendTechnology: null,
|
||||
backendTechnology: null,
|
||||
databaseTechnology: null,
|
||||
codeSource: null,
|
||||
paymentType: null,
|
||||
diskLink: null,
|
||||
@@ -484,6 +432,9 @@
|
||||
content = content.replace(/{projectName}/g, projectName);
|
||||
content = content.replace(/{codeDesc}/g, plainDesc);
|
||||
content = content.replace(/{codeEnvironment}/g, this.form.codeEnvironment || '');
|
||||
content = content.replace(/{frontendTechnology}/g, this.form.frontendTechnology || '');
|
||||
content = content.replace(/{backendTechnology}/g, this.form.backendTechnology || '');
|
||||
content = content.replace(/{databaseTechnology}/g, this.form.databaseTechnology || '');
|
||||
content = content.replace(/{codeTechnology}/g, this.form.codeTechnology || '');
|
||||
content = content.replace(/{diskLink}/g, this.form.diskLink || '');
|
||||
content = content.replace(/{screenshots}/g, screenshots);
|
||||
|
||||
@@ -89,7 +89,10 @@
|
||||
{projectName} 纯项目名(去掉编号和基于...实现的前缀)
|
||||
{codeDesc} 项目描述(纯文本)
|
||||
{codeEnvironment} 运行环境
|
||||
{codeTechnology} 项目技术
|
||||
{frontendTechnology} 前端
|
||||
{backendTechnology} 后端
|
||||
{databaseTechnology} 数据库
|
||||
{codeTechnology} 其他技术
|
||||
{diskLink} 网盘地址
|
||||
{screenshots} 截图列表(自动生成 markdown 格式)"
|
||||
/>
|
||||
@@ -102,7 +105,10 @@
|
||||
<code>{projectName}</code> 纯项目名
|
||||
<code>{codeDesc}</code> 描述(纯文本)
|
||||
<code>{codeEnvironment}</code> 运行环境
|
||||
<code>{codeTechnology}</code> 项目技术
|
||||
<code>{frontendTechnology}</code> 前端
|
||||
<code>{backendTechnology}</code> 后端
|
||||
<code>{databaseTechnology}</code> 数据库
|
||||
<code>{codeTechnology}</code> 其他技术
|
||||
<code>{diskLink}</code> 网盘地址
|
||||
<code>{screenshots}</code> 截图列表
|
||||
</template>
|
||||
@@ -229,4 +235,4 @@ export default {
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -82,6 +82,21 @@
|
||||
v-hasPermi="['office:project:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
@command="handleLinkImport"
|
||||
v-hasPermi="['office:project:edit']"
|
||||
>
|
||||
<el-button type="primary" plain icon="el-icon-upload2" size="mini">
|
||||
导入链接<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="QUARK">导入夸克链接</el-dropdown-item>
|
||||
<el-dropdown-item command="BAIDU">导入百度链接</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="info"
|
||||
@@ -97,10 +112,22 @@
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="projectList" @cell-dblclick="copyText" @selection-change="handleSelectionChange">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="projectList"
|
||||
@cell-dblclick="copyText"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="handleSortChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="${comment}" align="center" prop="id" />-->
|
||||
<el-table-column label="项目编号" align="center" prop="projectNum" />
|
||||
<el-table-column
|
||||
label="项目编号"
|
||||
align="center"
|
||||
prop="projectNum"
|
||||
sortable="custom"
|
||||
:sort-orders="['ascending', 'descending']"
|
||||
/>
|
||||
<el-table-column label="项目名称" align="center" prop="projectName" />
|
||||
<el-table-column label="源码名称" align="center" prop="projectName1" />
|
||||
<el-table-column label="项目技术" align="center" prop="projectNum1" />
|
||||
@@ -241,6 +268,94 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 网盘链接导入对话框 -->
|
||||
<el-dialog
|
||||
:title="linkUpload.title"
|
||||
:visible.sync="linkUpload.open"
|
||||
width="460px"
|
||||
append-to-body
|
||||
@closed="resetLinkUpload"
|
||||
>
|
||||
<el-upload
|
||||
ref="linkUpload"
|
||||
:limit="1"
|
||||
accept=".csv,.xls,.xlsx"
|
||||
:headers="linkUpload.headers"
|
||||
:action="linkUpload.url + '?diskType=' + linkUpload.diskType"
|
||||
:disabled="linkUpload.isUploading"
|
||||
:before-upload="beforeLinkFileUpload"
|
||||
:on-progress="handleLinkFileUploadProgress"
|
||||
:on-success="handleLinkFileSuccess"
|
||||
:on-error="handleLinkFileError"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<div class="el-upload__tip" slot="tip">
|
||||
支持网盘客户端导出的 csv、xls、xlsx 文件,单个文件不超过 5MB。
|
||||
<div class="import-rule-tip">
|
||||
按完整项目名称精确匹配;不存在时自动新增,存在时仅更新对应网盘链接。
|
||||
</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="linkUpload.isUploading"
|
||||
@click="submitLinkFileForm"
|
||||
>确 定</el-button>
|
||||
<el-button @click="linkUpload.open = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 网盘链接导入结果 -->
|
||||
<el-dialog
|
||||
title="导入结果"
|
||||
:visible.sync="importResult.open"
|
||||
width="760px"
|
||||
append-to-body
|
||||
>
|
||||
<div v-if="importResult.data" class="import-result">
|
||||
<el-alert
|
||||
:title="importResult.message"
|
||||
:type="importResult.data.failedCount || importResult.data.skippedCount ? 'warning' : 'success'"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<div class="import-result-summary">
|
||||
<span>总计:{{ importResult.data.totalCount || 0 }}</span>
|
||||
<span class="result-added">新增:{{ importResult.data.addedCount || 0 }}</span>
|
||||
<span class="result-updated">更新:{{ importResult.data.updatedCount || 0 }}</span>
|
||||
<span>未变化:{{ importResult.data.unchangedCount || 0 }}</span>
|
||||
<span class="result-skipped">跳过:{{ importResult.data.skippedCount || 0 }}</span>
|
||||
<span class="result-failed">失败:{{ importResult.data.failedCount || 0 }}</span>
|
||||
</div>
|
||||
<el-table
|
||||
v-if="importResult.data.details && importResult.data.details.length"
|
||||
:data="importResult.data.details"
|
||||
max-height="360"
|
||||
border
|
||||
size="small"
|
||||
>
|
||||
<el-table-column label="行号" prop="rowNumber" width="70" align="center" />
|
||||
<el-table-column label="项目名称" prop="projectName" min-width="210" show-overflow-tooltip />
|
||||
<el-table-column label="结果" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag
|
||||
size="mini"
|
||||
:type="scope.row.resultType === 'FAILED' ? 'danger' : 'warning'"
|
||||
>{{ scope.row.resultType === 'FAILED' ? '失败' : '跳过' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="原因" prop="reason" min-width="260" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="importResult.open = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 封面预览对话框 -->
|
||||
<el-dialog title="封面预览" :visible.sync="previewVisible" width="40%" append-to-body>
|
||||
<div class="preview-container" style="text-align: center;">
|
||||
@@ -297,6 +412,8 @@ export default {
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
orderByColumn: null,
|
||||
isAsc: null,
|
||||
projectNum: null,
|
||||
projectNum1: null,
|
||||
projectName: null,
|
||||
@@ -310,6 +427,21 @@ export default {
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {},
|
||||
// 网盘链接导入参数
|
||||
linkUpload: {
|
||||
open: false,
|
||||
title: '',
|
||||
diskType: '',
|
||||
isUploading: false,
|
||||
headers: { Authorization: 'Bearer ' + getToken() },
|
||||
url: process.env.VUE_APP_BASE_API + '/office/project/import-links'
|
||||
},
|
||||
// 网盘链接导入结果
|
||||
importResult: {
|
||||
open: false,
|
||||
message: '',
|
||||
data: null
|
||||
},
|
||||
previewVisible: false,
|
||||
previewUrl: '',
|
||||
previewBlob: null,
|
||||
@@ -365,6 +497,13 @@ export default {
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 项目编号排序 */
|
||||
handleSortChange({ prop, order }) {
|
||||
this.queryParams.orderByColumn = prop
|
||||
this.queryParams.isAsc = order
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 检测单个项目的网盘链接 */
|
||||
handleCheckRow(row) {
|
||||
if (this.isChecking(row.id)) {
|
||||
@@ -516,6 +655,73 @@ export default {
|
||||
...this.queryParams
|
||||
}, `project_${new Date().getTime()}.xlsx`)
|
||||
},
|
||||
/** 打开网盘链接导入对话框 */
|
||||
handleLinkImport(diskType) {
|
||||
this.linkUpload.diskType = diskType
|
||||
this.linkUpload.title = diskType === 'QUARK' ? '导入夸克网盘链接' : '导入百度网盘链接'
|
||||
this.linkUpload.open = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.linkUpload) {
|
||||
this.$refs.linkUpload.clearFiles()
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 导入文件校验 */
|
||||
beforeLinkFileUpload(file) {
|
||||
const extension = file.name.substring(file.name.lastIndexOf('.')).toLowerCase()
|
||||
const isSupported = ['.csv', '.xls', '.xlsx'].indexOf(extension) !== -1
|
||||
const isWithinLimit = file.size / 1024 / 1024 <= 5
|
||||
if (!isSupported) {
|
||||
this.$modal.msgError('仅支持 csv、xls、xlsx 格式文件')
|
||||
return false
|
||||
}
|
||||
if (!isWithinLimit) {
|
||||
this.$modal.msgError('导入文件不能超过 5MB')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
/** 网盘链接文件上传中 */
|
||||
handleLinkFileUploadProgress() {
|
||||
this.linkUpload.isUploading = true
|
||||
},
|
||||
/** 网盘链接文件上传成功 */
|
||||
handleLinkFileSuccess(response) {
|
||||
this.linkUpload.isUploading = false
|
||||
if (!response || response.code !== 200) {
|
||||
this.$modal.msgError(response && response.msg ? response.msg : '导入失败')
|
||||
return
|
||||
}
|
||||
this.linkUpload.open = false
|
||||
this.importResult.message = response.msg || '导入完成'
|
||||
this.importResult.data = response.data || {}
|
||||
this.importResult.open = true
|
||||
this.getList()
|
||||
},
|
||||
/** 网盘链接文件上传失败 */
|
||||
handleLinkFileError(error) {
|
||||
this.linkUpload.isUploading = false
|
||||
let message = '导入失败'
|
||||
if (error && error.message) {
|
||||
message = error.message
|
||||
}
|
||||
this.$modal.msgError(message)
|
||||
},
|
||||
/** 提交网盘链接导入文件 */
|
||||
submitLinkFileForm() {
|
||||
if (!this.$refs.linkUpload || !this.$refs.linkUpload.uploadFiles.length) {
|
||||
this.$modal.msgWarning('请选择需要导入的文件')
|
||||
return
|
||||
}
|
||||
this.$refs.linkUpload.submit()
|
||||
},
|
||||
/** 重置网盘链接上传状态 */
|
||||
resetLinkUpload() {
|
||||
this.linkUpload.isUploading = false
|
||||
if (this.$refs.linkUpload) {
|
||||
this.$refs.linkUpload.clearFiles()
|
||||
}
|
||||
},
|
||||
copyText(row, column, cell, event) {
|
||||
if (column.label !== '操作') {
|
||||
// 双击复制
|
||||
@@ -534,14 +740,14 @@ export default {
|
||||
let content = ''
|
||||
if (row.projectUrl) {
|
||||
content += '夸克网盘\n'
|
||||
content += '链接:'+ row.projectUrl
|
||||
content += '链接:' + row.projectUrl
|
||||
}
|
||||
if (content.length > 0) {
|
||||
content += '\n\n'
|
||||
}
|
||||
if (row.projectBaiduUrl) {
|
||||
content += '百度网盘\n'
|
||||
content += '链接:'+ row.projectBaiduUrl
|
||||
content += '链接:' + row.projectBaiduUrl
|
||||
}
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
this.$message({
|
||||
@@ -559,14 +765,14 @@ export default {
|
||||
let content = ''
|
||||
if (row.projectUrl) {
|
||||
content += '夸克网盘\n'
|
||||
content += '链接:'+ row.projectUrl
|
||||
content += '链接:' + row.projectUrl
|
||||
}
|
||||
if (content.length > 0) {
|
||||
content += '\n\n'
|
||||
}
|
||||
if (row.projectBaiduUrl) {
|
||||
content += '百度网盘\n'
|
||||
content += '链接:'+ row.projectBaiduUrl
|
||||
content += '链接:' + row.projectBaiduUrl
|
||||
}
|
||||
content += '\n\n项目部署文档\nhttps://www.yuque.com/feastcoding/cg5w9h/rbvp8w80h8tultgy 密码:el4q'
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
@@ -655,4 +861,31 @@ export default {
|
||||
color: #606266;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.import-rule-tip {
|
||||
margin-top: 6px;
|
||||
color: #909399;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.import-result-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 18px;
|
||||
margin: 18px 0;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.result-added,
|
||||
.result-updated {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.result-skipped {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.result-failed {
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user