Compare commits
No commits in common. '86abe6ee5fc11c2fd1a9352ffddc114e5c17ed74' and '12f80d3310402e1b436386c7c8ade4f11d31904e' have entirely different histories.
86abe6ee5f
...
12f80d3310
@ -1,128 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="order-type-chart">
|
|
||||||
<div class="chart-header">
|
|
||||||
<h3>订单类型分布</h3>
|
|
||||||
</div>
|
|
||||||
<v-chart class="chart" :option="chartOption" autoresize />
|
|
||||||
<!-- <div class="legend">
|
|
||||||
<div class="legend-item" v-for="item in legendData" :key="item.name">
|
|
||||||
<span class="legend-color" :style="{ backgroundColor: item.color }"></span>
|
|
||||||
<span class="legend-text">{{ item.name }}</span>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { use } from 'echarts/core'
|
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
|
||||||
import { PieChart } from 'echarts/charts'
|
|
||||||
import { TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components'
|
|
||||||
import VChart from 'vue-echarts'
|
|
||||||
import RPC from '@/utils/rpc'
|
|
||||||
|
|
||||||
use([CanvasRenderer, PieChart, TitleComponent, TooltipComponent, LegendComponent])
|
|
||||||
|
|
||||||
const legendData = ref([
|
|
||||||
{ name: '充值', color: '#409eff' },
|
|
||||||
{ name: '包月', color: '#67c23a' },
|
|
||||||
{ name: '包季', color: '#e6a23c' },
|
|
||||||
{ name: '包年', color: '#f56c6c' },
|
|
||||||
])
|
|
||||||
|
|
||||||
RPC.post('/dashboard/order_type_distribution').then((res) => {
|
|
||||||
console.log('order_type_distribution', res)
|
|
||||||
chartData.value = res.items.map((item) => {
|
|
||||||
return {
|
|
||||||
name: item.type,
|
|
||||||
value: item.value_cents / 100,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const chartData = ref([
|
|
||||||
// { value: 35, name: '充值', itemStyle: { color: '#409eff' } },
|
|
||||||
// { value: 25, name: '包月', itemStyle: { color: '#67c23a' } },
|
|
||||||
// { value: 25, name: '包季', itemStyle: { color: '#e6a23c' } },
|
|
||||||
// { value: 15, name: '包年', itemStyle: { color: '#f56c6c' } },
|
|
||||||
])
|
|
||||||
|
|
||||||
const chartOption = ref({
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
formatter: '{a} <br/>{b}: {c}% ({d}%)',
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
bottom: '0%',
|
|
||||||
left: 'center',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '订单类型',
|
|
||||||
type: 'pie',
|
|
||||||
radius: ['50%', '80%'],
|
|
||||||
center: ['50%', '50%'],
|
|
||||||
avoidLabelOverlap: false,
|
|
||||||
label: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
label: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
labelLine: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
data: chartData,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.order-type-chart {
|
|
||||||
padding: 20px;
|
|
||||||
|
|
||||||
.chart-header {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
|
||||||
height: 300px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 24px;
|
|
||||||
margin-top: 20px;
|
|
||||||
|
|
||||||
.legend-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
.legend-color {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-text {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #606266;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="recent-orders">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3>最近订单</h3>
|
|
||||||
<el-button type="primary" size="small" plain @click="goAll">查看全部</el-button>
|
|
||||||
</div>
|
|
||||||
<div class="table-container">
|
|
||||||
<el-table :data="orderData" style="width: 100%" size="small" height="270">
|
|
||||||
<el-table-column align="center" prop="order_no" label="订单编号" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" prop="user_name" label="用户" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" prop="amount" label="金额" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<span class="amount">{{ scope.row.amount }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" prop="order_type" label="类型" width="100">
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" prop="status" label="状态" width="100">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag v-if="row.status === 'created'" type="warning" size="small">待付款</el-tag>
|
|
||||||
<el-tag v-if="row.status === 'pending'" type="warning" size="small">待支付</el-tag>
|
|
||||||
<el-tag v-if="row.status === 'paid'" type="success" size="small">已支付</el-tag>
|
|
||||||
<el-tag v-if="row.status === 'failed'" type="danger" size="small">支付失败</el-tag>
|
|
||||||
<el-tag v-if="row.status === 'canceled'" type="info" size="small">已取消</el-tag>
|
|
||||||
<el-tag v-if="row.status === 'refunded'" type="info" size="small">已退款</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import RPC from '@/utils/rpc'
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
const router = useRouter()
|
|
||||||
const goAll = () => router.push('/OrderManage')
|
|
||||||
|
|
||||||
const orderData = ref([])
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const getList = () => {
|
|
||||||
loading.value = true
|
|
||||||
RPC.post('/membership/order/list', { page: 1, size: 10 })
|
|
||||||
.then((res) => {
|
|
||||||
orderData.value = res.list
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
getList()
|
|
||||||
|
|
||||||
const getTypeTagType = (type) => {
|
|
||||||
const typeMap = {
|
|
||||||
包年: '',
|
|
||||||
包季: 'success',
|
|
||||||
充值: 'info',
|
|
||||||
包月: 'warning',
|
|
||||||
}
|
|
||||||
return typeMap[type] || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStatusTagType = (status) => {
|
|
||||||
const statusMap = {
|
|
||||||
已支付: 'success',
|
|
||||||
待支付: 'warning',
|
|
||||||
已取消: 'danger',
|
|
||||||
}
|
|
||||||
return statusMap[status] || ''
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.recent-orders {
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-container {
|
|
||||||
.amount {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-table) {
|
|
||||||
.el-table__header {
|
|
||||||
th {
|
|
||||||
background-color: #fafafa;
|
|
||||||
color: #606266;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-table__row {
|
|
||||||
&:hover {
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,181 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="revenue-chart">
|
|
||||||
<div class="chart-header">
|
|
||||||
<h3>收入趋势</h3>
|
|
||||||
<div class="chart-tabs">
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
:class="{ active: activeTab === 'month' }"
|
|
||||||
@click="switchTab('month')"
|
|
||||||
>
|
|
||||||
月度
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
:class="{ active: activeTab === 'quarter' }"
|
|
||||||
@click="switchTab('quarter')"
|
|
||||||
>
|
|
||||||
季度
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
:class="{ active: activeTab === 'year' }"
|
|
||||||
@click="switchTab('year')"
|
|
||||||
>
|
|
||||||
年度
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<v-chart class="chart" :option="chartOption" autoresize />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
import { use } from 'echarts/core'
|
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
|
||||||
import { LineChart } from 'echarts/charts'
|
|
||||||
import {
|
|
||||||
TitleComponent,
|
|
||||||
TooltipComponent,
|
|
||||||
LegendComponent,
|
|
||||||
GridComponent,
|
|
||||||
} from 'echarts/components'
|
|
||||||
import VChart from 'vue-echarts'
|
|
||||||
import RPC from '@/utils/rpc'
|
|
||||||
|
|
||||||
use([CanvasRenderer, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
|
||||||
|
|
||||||
const activeTab = ref('month')
|
|
||||||
|
|
||||||
const switchTab = (tab) => {
|
|
||||||
activeTab.value = tab
|
|
||||||
console.log(activeTab.value)
|
|
||||||
getData()
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = ref([])
|
|
||||||
const getData = () => {
|
|
||||||
RPC.post('/dashboard/revenue_trend', { granularity: activeTab.value }).then((res) => {
|
|
||||||
console.log(activeTab.value, res)
|
|
||||||
data.value = res.items || []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
getData()
|
|
||||||
|
|
||||||
const chartOption = computed(() => {
|
|
||||||
return {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
formatter: '{b}: ¥{c}',
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: '3%',
|
|
||||||
right: '4%',
|
|
||||||
bottom: '3%',
|
|
||||||
containLabel: true,
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
boundaryGap: false,
|
|
||||||
data: data.value.map((item) => item.label),
|
|
||||||
axisLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#e4e7ed',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: '#606266',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
axisLine: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: '#606266',
|
|
||||||
formatter: '{value}',
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#f5f7fa',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
data: data.value.map((item) => item.value_cents / 100),
|
|
||||||
smooth: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#409eff',
|
|
||||||
width: 3,
|
|
||||||
},
|
|
||||||
itemStyle: {
|
|
||||||
color: '#409eff',
|
|
||||||
},
|
|
||||||
areaStyle: {
|
|
||||||
color: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
x2: 0,
|
|
||||||
y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{
|
|
||||||
offset: 0,
|
|
||||||
color: 'rgba(64, 158, 255, 0.3)',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
offset: 1,
|
|
||||||
color: 'rgba(64, 158, 255, 0.1)',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.revenue-chart {
|
|
||||||
padding: 20px;
|
|
||||||
|
|
||||||
.chart-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-tabs {
|
|
||||||
.el-button {
|
|
||||||
margin-left: 8px;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: #157ee8;
|
|
||||||
border-color: #177de3;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
|
||||||
height: 300px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,295 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="dashboard">
|
|
||||||
<!-- 统计卡片 -->
|
|
||||||
<div class="stats-cards">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-content">
|
|
||||||
<div class="stat-info">
|
|
||||||
<div class="stat-label">总用户数</div>
|
|
||||||
<div class="stat-value">{{ data.total_users }}</div>
|
|
||||||
<div
|
|
||||||
class="stat-trend"
|
|
||||||
:class="{
|
|
||||||
positive: data.total_users_mom_ratio?.includes('+'),
|
|
||||||
negative:
|
|
||||||
data.total_users_mom_ratio?.includes('-') &&
|
|
||||||
data.total_users_mom_ratio.length > 1,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<el-icon v-if="data.total_users_mom_ratio?.includes('+')"><ArrowUp /></el-icon>
|
|
||||||
<el-icon
|
|
||||||
v-if="
|
|
||||||
data.total_users_mom_ratio?.includes('-') && data.total_users_mom_ratio.length > 1
|
|
||||||
"
|
|
||||||
><ArrowDown
|
|
||||||
/></el-icon>
|
|
||||||
{{ data.total_users_mom_ratio }} 较上月
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-icon user">
|
|
||||||
<el-icon><User /></el-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-content">
|
|
||||||
<div class="stat-info">
|
|
||||||
<div class="stat-label">今日活跃</div>
|
|
||||||
<div class="stat-value">{{ data.active_today }}</div>
|
|
||||||
<div
|
|
||||||
class="stat-trend"
|
|
||||||
:class="{
|
|
||||||
positive: data.active_today_dod_ratio?.includes('+'),
|
|
||||||
negative:
|
|
||||||
data.active_today_dod_ratio?.includes('-') &&
|
|
||||||
data.active_today_dod_ratio.length > 1,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<el-icon v-if="data.active_today_dod_ratio?.includes('+')"><ArrowUp /></el-icon>
|
|
||||||
<el-icon
|
|
||||||
v-if="
|
|
||||||
data.active_today_dod_ratio?.includes('-') &&
|
|
||||||
data.active_today_dod_ratio.length > 1
|
|
||||||
"
|
|
||||||
><ArrowDown
|
|
||||||
/></el-icon>
|
|
||||||
{{ data.active_today_dod_ratio }} 较昨日
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-icon active">
|
|
||||||
<el-icon><TrendCharts /></el-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-content">
|
|
||||||
<div class="stat-info">
|
|
||||||
<div class="stat-label">商品生成量</div>
|
|
||||||
<div class="stat-value">{{ data.drawings_generated }}</div>
|
|
||||||
<div
|
|
||||||
class="stat-trend"
|
|
||||||
:class="{
|
|
||||||
positive: data.drawings_generated_mom_ratio?.includes('+'),
|
|
||||||
negative:
|
|
||||||
data.drawings_generated_mom_ratio?.includes('-') &&
|
|
||||||
data.drawings_generated_mom_ratio?.length > 1,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<el-icon v-if="data.drawings_generated_mom_ratio?.includes('+')"><ArrowUp /></el-icon>
|
|
||||||
<el-icon
|
|
||||||
v-if="
|
|
||||||
data.drawings_generated_mom_ratio?.includes('-') &&
|
|
||||||
data.drawings_generated_mom_ratio?.length > 1
|
|
||||||
"
|
|
||||||
><ArrowDown
|
|
||||||
/></el-icon>
|
|
||||||
{{ data.drawings_generated_mom_ratio }} 较上月
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-icon generate">
|
|
||||||
<el-icon><Document /></el-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-content">
|
|
||||||
<div class="stat-info">
|
|
||||||
<div class="stat-label">本月收入</div>
|
|
||||||
<div class="stat-value">¥{{ data.month_income_cents / 100 }}</div>
|
|
||||||
<div
|
|
||||||
class="stat-trend"
|
|
||||||
:class="{
|
|
||||||
positive: data.month_income_cents_mom_ratio?.includes('+'),
|
|
||||||
negative:
|
|
||||||
data.month_income_cents_mom_ratio?.includes('-') &&
|
|
||||||
data.month_income_cents_mom_ratio?.length > 1,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<el-icon v-if="data.month_income_cents_mom_ratio?.includes('+')"><ArrowUp /></el-icon>
|
|
||||||
<el-icon
|
|
||||||
v-if="
|
|
||||||
data.month_income_cents_mom_ratio?.includes('-') &&
|
|
||||||
data.month_income_cents_mom_ratio?.length > 1
|
|
||||||
"
|
|
||||||
><ArrowDown
|
|
||||||
/></el-icon>
|
|
||||||
{{ data.month_income_cents_mom_ratio }} 较上月
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-icon revenue">
|
|
||||||
<el-icon><Money /></el-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 图表区域 -->
|
|
||||||
<div class="charts-section">
|
|
||||||
<div class="chart-container">
|
|
||||||
<RevenueChart />
|
|
||||||
</div>
|
|
||||||
<div class="chart-container">
|
|
||||||
<OrderTypeChart />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 最近数据区域 -->
|
|
||||||
<div class="recent-section">
|
|
||||||
<div class="recent-container">
|
|
||||||
<RecentOrders />
|
|
||||||
</div>
|
|
||||||
<div class="recent-container">
|
|
||||||
<RecentDrawings />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ArrowUp, ArrowDown, User, TrendCharts, Document, Money } from '@element-plus/icons-vue'
|
|
||||||
import RevenueChart from './RevenueChart.vue'
|
|
||||||
import OrderTypeChart from './OrderTypeChart.vue'
|
|
||||||
import RecentOrders from './RecentOrders.vue'
|
|
||||||
import RecentDrawings from './RecentDrawings.vue'
|
|
||||||
import RPC from '@/utils/rpc'
|
|
||||||
|
|
||||||
const data = ref({})
|
|
||||||
const getData = () => {
|
|
||||||
RPC.post('/dashboard/summary').then((res) => {
|
|
||||||
data.value = res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
getData()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.dashboard {
|
|
||||||
padding: 20px;
|
|
||||||
// background-color: #f5f7fa;
|
|
||||||
min-height: 100vh;
|
|
||||||
|
|
||||||
.stats-cards {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 24px;
|
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.stat-info {
|
|
||||||
.stat-label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #909399;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-trend {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
&.positive {
|
|
||||||
color: #67c23a;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.negative {
|
|
||||||
color: #f56c6c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-icon {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
border-radius: 12px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
font-size: 24px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.user {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.generate {
|
|
||||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.revenue {
|
|
||||||
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.charts-section {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 2fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-container {
|
|
||||||
min-height: 400px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.recent-section {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recent-container {
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import RPC from '@/utils/rpc'
|
|
||||||
const props = defineProps({
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
row: [Object],
|
|
||||||
templateList: [Array],
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['closed', 'onSuccess'])
|
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
|
|
||||||
const isCancel = computed(() => props.type === 'cancel')
|
|
||||||
const isApprove = computed(() => props.type === 'approve')
|
|
||||||
const isReject = computed(() => props.type === 'reject')
|
|
||||||
const isApproval = computed(() => isApprove.value || isReject.value)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
dialogVisible.value = true
|
|
||||||
})
|
|
||||||
|
|
||||||
const formRef = ref(null)
|
|
||||||
const formField = ref({ remark: '' })
|
|
||||||
const formRules = reactive({
|
|
||||||
remark: [{ required: true, message: '备注不可为空', trigger: 'change' }],
|
|
||||||
})
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
function closed() {
|
|
||||||
emit('closed')
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitForm = async () => {
|
|
||||||
await formRef.value.validate()
|
|
||||||
|
|
||||||
if (isApproval.value) {
|
|
||||||
// 审批逻辑
|
|
||||||
const params = {
|
|
||||||
application_id: props.row.id,
|
|
||||||
action: isApprove.value ? 'approve' : 'reject',
|
|
||||||
remark: formField.value.remark,
|
|
||||||
}
|
|
||||||
loading.value = true
|
|
||||||
RPC.post('/template_access_review', params)
|
|
||||||
.then((res) => {
|
|
||||||
ElMessage.success('操作成功')
|
|
||||||
emit('onSuccess')
|
|
||||||
emit('closed')
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const params = {
|
|
||||||
template_id: props.row.template_id,
|
|
||||||
user_id: props.row.user_id,
|
|
||||||
remark: formField.value.remark,
|
|
||||||
}
|
|
||||||
loading.value = true
|
|
||||||
RPC.post('/template_access_revoke', params)
|
|
||||||
.then((res) => {
|
|
||||||
ElMessage.success('操作成功')
|
|
||||||
emit('onSuccess')
|
|
||||||
emit('closed')
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
align-center
|
|
||||||
:title="isApprove ? '同意申请' : isReject ? '拒绝申请' : isCancel ? '撤回权限' : ''"
|
|
||||||
:width="isApproval ? '500px' : '80%'"
|
|
||||||
v-model="dialogVisible"
|
|
||||||
@closed="closed"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
>
|
|
||||||
<el-form
|
|
||||||
ref="formRef"
|
|
||||||
label-position="right"
|
|
||||||
label-suffix=":"
|
|
||||||
:model="formField"
|
|
||||||
:rules="formRules"
|
|
||||||
v-loading="loading"
|
|
||||||
label-width="150"
|
|
||||||
>
|
|
||||||
<!-- 审批表单 -->
|
|
||||||
<template v-if="isApproval">
|
|
||||||
<el-form-item label="申请用户">
|
|
||||||
<span>{{ row?.nickname }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="申请原因">
|
|
||||||
<span>{{ row?.reason }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="remark" label="备注">
|
|
||||||
<el-input type="textarea" :rows="4" placeholder="请输入备注" v-model="formField.remark" />
|
|
||||||
</el-form-item>
|
|
||||||
</template>
|
|
||||||
<template v-else="isApproval">
|
|
||||||
<el-form-item label="撤回用户">
|
|
||||||
<span>{{ row?.nickname || row?.user_id }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="撤回模板">
|
|
||||||
<span>
|
|
||||||
{{
|
|
||||||
templateList?.find((item) => item.id === row.template_id)?.name || row.template_id
|
|
||||||
}}</span
|
|
||||||
>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="remark" label="备注">
|
|
||||||
<el-input type="textarea" :rows="4" placeholder="请输入备注" v-model="formField.remark" />
|
|
||||||
</el-form-item>
|
|
||||||
</template>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button @click="closed"> 取 消 </el-button>
|
|
||||||
<el-button :type="isReject ? 'danger' : 'primary'" :loading="loading" @click="submitForm">
|
|
||||||
{{ isApprove ? '同意' : isReject ? '拒绝' : '确定' }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
@ -1,175 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="title">模板审批</div>
|
|
||||||
<el-form
|
|
||||||
ref="searchFormRef"
|
|
||||||
class="form"
|
|
||||||
:inline="true"
|
|
||||||
label-width="98px"
|
|
||||||
:model="queryParams"
|
|
||||||
>
|
|
||||||
<el-form-item label="申请用户" prop="nickname">
|
|
||||||
<el-input
|
|
||||||
clearable
|
|
||||||
placeholder="请输入"
|
|
||||||
style="width: 220px"
|
|
||||||
v-model="queryParams.nickname"
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label-width="100px">
|
|
||||||
<el-button icon="Search" type="primary" @click="handleQuery"> 搜索 </el-button>
|
|
||||||
<el-button icon="RefreshRight" @click="resetQuery"> 重置 </el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<div style="margin-bottom: 20px">
|
|
||||||
<!-- <el-button icon="Plus" type="primary" @click="add"> 新建 </el-button> -->
|
|
||||||
</div>
|
|
||||||
<div ref="tableContainerEl" class="table_border table">
|
|
||||||
<el-table
|
|
||||||
ref="tableRef"
|
|
||||||
:data="tableData"
|
|
||||||
v-loading="loading"
|
|
||||||
header-row-class-name="table-header"
|
|
||||||
>
|
|
||||||
<el-table-column align="center" label="申请用户" prop="nickname" show-overflow-tooltip>
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.nickname || row.user_id }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="申请原因" prop="reason" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="申请模板" prop="template_id" show-overflow-tooltip>
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ templateList.find((item) => item.id === row.template_id)?.name || row.template_id }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="状态" prop="status">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span v-if="row.status === 'pending'">申请中</span>
|
|
||||||
<span v-if="row.status === 'rejected'">已拒绝</span>
|
|
||||||
<span v-if="row.status === 'approved'">已同意</span>
|
|
||||||
<span v-if="row.status === 'revoked'">已撤回</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="备注" prop="remark" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="操作" width="120">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button
|
|
||||||
v-if="row.status === 'pending'"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
@click="handleApproval(row, 'approve')"
|
|
||||||
>
|
|
||||||
同意
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="row.status === 'approved'"
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
@click="handleApproval(row, 'cancel')"
|
|
||||||
>
|
|
||||||
撤回权限
|
|
||||||
</el-button>
|
|
||||||
|
|
||||||
<el-button
|
|
||||||
v-if="row.status === 'pending'"
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
@click="handleApproval(row, 'reject')"
|
|
||||||
>
|
|
||||||
拒绝
|
|
||||||
</el-button>
|
|
||||||
<span v-if="row.status !== 'pending' && row.status !== 'approved'">--</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<pagination
|
|
||||||
v-show="total > 0"
|
|
||||||
:total="total"
|
|
||||||
v-model:limit="queryParams.limit"
|
|
||||||
v-model:page="queryParams.page"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HandleDialog
|
|
||||||
v-if="isShowDialog"
|
|
||||||
:type="rowType"
|
|
||||||
:row="currentRow"
|
|
||||||
@closed="isShowDialog = false"
|
|
||||||
@on-success="getList"
|
|
||||||
:templateList="templateList"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { useTableRPC } from '@/utils/hook'
|
|
||||||
import HandleDialog from './HandleDialog.vue'
|
|
||||||
import RPC from '@/utils/rpc.js'
|
|
||||||
|
|
||||||
const rowType = ref('')
|
|
||||||
const currentRow = ref(null)
|
|
||||||
const add = () => {
|
|
||||||
isShowDialog.value = true
|
|
||||||
currentRow.value = null
|
|
||||||
rowType.value = 'add'
|
|
||||||
}
|
|
||||||
const edit = (row) => {
|
|
||||||
isShowDialog.value = true
|
|
||||||
rowType.value = 'edit'
|
|
||||||
currentRow.value = row
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateList = ref([])
|
|
||||||
RPC.post('/template/list', { page: 1, limit: -1 }).then((res) => {
|
|
||||||
templateList.value = res.rows || []
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
|
||||||
loading,
|
|
||||||
tableData,
|
|
||||||
tableRef,
|
|
||||||
searchFormRef,
|
|
||||||
total,
|
|
||||||
queryParams,
|
|
||||||
getList,
|
|
||||||
handleQuery,
|
|
||||||
resetQuery,
|
|
||||||
handleDelete,
|
|
||||||
isShowDialog,
|
|
||||||
maxHeight,
|
|
||||||
tableContainerEl,
|
|
||||||
} = useTableRPC({
|
|
||||||
queryParams: {
|
|
||||||
orderby: [{ sort: 'asc' }],
|
|
||||||
},
|
|
||||||
remoteDataFn: '/template/access/list',
|
|
||||||
// deleteConfig: {
|
|
||||||
// remote: '/vendor/delete',
|
|
||||||
// key: 'id',
|
|
||||||
// message(row) {
|
|
||||||
// return `确认删除 “${row.name}”`
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleApproval = (row, action) => {
|
|
||||||
isShowDialog.value = true
|
|
||||||
currentRow.value = row
|
|
||||||
rowType.value = action
|
|
||||||
}
|
|
||||||
|
|
||||||
getList()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.title {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import RPC from '@/utils/rpc'
|
|
||||||
const props = defineProps({
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
row: [Object],
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['closed', 'onSuccess'])
|
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
|
|
||||||
const isAdd = computed(() => props.type === 'add')
|
|
||||||
const isApprove = computed(() => props.type === 'approve')
|
|
||||||
const isReject = computed(() => props.type === 'reject')
|
|
||||||
const isApproval = computed(() => isApprove.value || isReject.value)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
dialogVisible.value = true
|
|
||||||
})
|
|
||||||
|
|
||||||
const formRef = ref(null)
|
|
||||||
const formField = ref({ enabled: true, params: [], remark: '' })
|
|
||||||
const formRules = reactive({
|
|
||||||
name: [{ required: true, message: '不可为空', trigger: 'change' }],
|
|
||||||
code: [{ required: true, message: '不可为空', trigger: 'change' }],
|
|
||||||
remark: [{ required: true, message: '备注不可为空', trigger: 'change' }],
|
|
||||||
})
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
function closed() {
|
|
||||||
emit('closed')
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitForm = async () => {
|
|
||||||
await formRef.value.validate()
|
|
||||||
|
|
||||||
if (isApproval.value) {
|
|
||||||
// 审批逻辑
|
|
||||||
const params = {
|
|
||||||
application_id: props.row.id,
|
|
||||||
action: isApprove.value ? 'approve' : 'reject',
|
|
||||||
remark: formField.value.remark
|
|
||||||
}
|
|
||||||
loading.value = true
|
|
||||||
RPC.post('/template_access_review', params)
|
|
||||||
.then((res) => {
|
|
||||||
ElMessage.success('操作成功')
|
|
||||||
emit('onSuccess')
|
|
||||||
emit('closed')
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// 原有的增删改逻辑
|
|
||||||
const api = isAdd.value ? '/vendor/create' : '/vendor/update'
|
|
||||||
const params = {
|
|
||||||
...formField.value,
|
|
||||||
id: isAdd.value ? null : props.row.id,
|
|
||||||
}
|
|
||||||
loading.value = true
|
|
||||||
RPC.post(api, params)
|
|
||||||
.then((res) => {
|
|
||||||
ElMessage.success('操作成功')
|
|
||||||
emit('onSuccess')
|
|
||||||
emit('closed')
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAdd.value && !isApproval.value) {
|
|
||||||
formField.value = {
|
|
||||||
...props.row,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
align-center
|
|
||||||
:title="isApprove ? '同意申请' : isReject ? '拒绝申请' : isAdd ? '新增' : '修改'"
|
|
||||||
:width="isApproval ? '500px' : '80%'"
|
|
||||||
v-model="dialogVisible"
|
|
||||||
@closed="closed"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
>
|
|
||||||
<el-form
|
|
||||||
ref="formRef"
|
|
||||||
label-position="right"
|
|
||||||
label-suffix=":"
|
|
||||||
:model="formField"
|
|
||||||
:rules="formRules"
|
|
||||||
v-loading="loading"
|
|
||||||
label-width="150"
|
|
||||||
>
|
|
||||||
<!-- 审批表单 -->
|
|
||||||
<template v-if="isApproval">
|
|
||||||
<el-form-item label="申请用户">
|
|
||||||
<span>{{ row?.nickname }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="申请原因">
|
|
||||||
<span>{{ row?.reason }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="remark" label="备注">
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
:rows="4"
|
|
||||||
placeholder="请输入备注"
|
|
||||||
v-model="formField.remark"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</template>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button @click="closed"> 取 消 </el-button>
|
|
||||||
<el-button
|
|
||||||
:type="isReject ? 'danger' : 'primary'"
|
|
||||||
:loading="loading"
|
|
||||||
@click="submitForm"
|
|
||||||
>
|
|
||||||
{{ isApprove ? '同意' : isReject ? '拒绝' : '确定' }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="title">需求管理</div>
|
|
||||||
<el-form
|
|
||||||
ref="searchFormRef"
|
|
||||||
class="form"
|
|
||||||
:inline="true"
|
|
||||||
label-width="98px"
|
|
||||||
:model="queryParams"
|
|
||||||
>
|
|
||||||
<el-form-item label="名称" prop="name">
|
|
||||||
<el-input
|
|
||||||
clearable
|
|
||||||
placeholder="请输入"
|
|
||||||
style="width: 220px"
|
|
||||||
v-model="queryParams.name"
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="分类" prop="category">
|
|
||||||
<el-input
|
|
||||||
clearable
|
|
||||||
placeholder="请输入"
|
|
||||||
style="width: 220px"
|
|
||||||
v-model="queryParams.category"
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label-width="100px">
|
|
||||||
<el-button icon="Search" type="primary" @click="handleQuery"> 搜索 </el-button>
|
|
||||||
<el-button icon="RefreshRight" @click="resetQuery"> 重置 </el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<div style="margin-bottom: 20px">
|
|
||||||
<!-- <el-button icon="Plus" type="primary" @click="add"> 新建 </el-button> -->
|
|
||||||
</div>
|
|
||||||
<div ref="tableContainerEl" class="table_border table">
|
|
||||||
<el-table
|
|
||||||
ref="tableRef"
|
|
||||||
:data="tableData"
|
|
||||||
v-loading="loading"
|
|
||||||
header-row-class-name="table-header"
|
|
||||||
>
|
|
||||||
<el-table-column align="center" label="分类" prop="category" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="名称" prop="name" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="描述" prop="description" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="提出用户" prop="proposer" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="邮箱" prop="email" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="手机" prop="phone" show-overflow-tooltip />
|
|
||||||
<el-table-column
|
|
||||||
align="center"
|
|
||||||
label="联系厂商"
|
|
||||||
prop="vendor_contact"
|
|
||||||
show-overflow-tooltip
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
align="center"
|
|
||||||
label="模板名称"
|
|
||||||
prop="template_name"
|
|
||||||
show-overflow-tooltip
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-table-column align="center" label="操作" width="120">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button link type="danger" @click="handleDelete(row)"> 删除 </el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<pagination
|
|
||||||
v-show="total > 0"
|
|
||||||
:total="total"
|
|
||||||
v-model:limit="queryParams.limit"
|
|
||||||
v-model:page="queryParams.page"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HandleDialog
|
|
||||||
v-if="isShowDialog"
|
|
||||||
:type="rowType"
|
|
||||||
:row="currentRow"
|
|
||||||
@closed="isShowDialog = false"
|
|
||||||
@on-success="getList"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { useTableRPC } from '@/utils/hook'
|
|
||||||
import HandleDialog from './HandleDialog.vue'
|
|
||||||
import RPC from '@/utils/rpc.js'
|
|
||||||
|
|
||||||
const {
|
|
||||||
loading,
|
|
||||||
tableData,
|
|
||||||
tableRef,
|
|
||||||
searchFormRef,
|
|
||||||
total,
|
|
||||||
queryParams,
|
|
||||||
getList,
|
|
||||||
handleQuery,
|
|
||||||
resetQuery,
|
|
||||||
handleDelete,
|
|
||||||
isShowDialog,
|
|
||||||
maxHeight,
|
|
||||||
tableContainerEl,
|
|
||||||
} = useTableRPC({
|
|
||||||
queryParams: {
|
|
||||||
orderby: [{ id: 'desc' }],
|
|
||||||
},
|
|
||||||
remoteDataFn: '/requirement/list',
|
|
||||||
deleteConfig: {
|
|
||||||
remote: '/requirement/delete',
|
|
||||||
key: 'id',
|
|
||||||
message(row) {
|
|
||||||
return `确认删除 “${row.name}”`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
getList()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.title {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import RPC from '@/utils/rpc'
|
|
||||||
const props = defineProps({
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
row: [Object],
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['closed', 'onSuccess'])
|
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
|
|
||||||
const isAdd = computed(() => props.type === 'add')
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
dialogVisible.value = true
|
|
||||||
})
|
|
||||||
|
|
||||||
const formRef = ref(null)
|
|
||||||
const formField = ref({ enabled: true })
|
|
||||||
const formRules = reactive({
|
|
||||||
name: [{ required: true, message: '不可为空', trigger: 'change' }],
|
|
||||||
code: [{ required: true, message: '不可为空', trigger: 'change' }],
|
|
||||||
})
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
function closed() {
|
|
||||||
emit('closed')
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitForm = async () => {
|
|
||||||
await formRef.value.validate()
|
|
||||||
const api = isAdd.value ? '/vendor/create' : '/vendor/update'
|
|
||||||
const params = {
|
|
||||||
...formField.value,
|
|
||||||
id: isAdd.value ? null : props.row.id,
|
|
||||||
}
|
|
||||||
loading.value = true
|
|
||||||
RPC.post(api, params)
|
|
||||||
.then((res) => {
|
|
||||||
ElMessage.success('操作成功')
|
|
||||||
emit('onSuccess')
|
|
||||||
emit('closed')
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAdd.value) {
|
|
||||||
formField.value = {
|
|
||||||
...props.row,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
align-center
|
|
||||||
:title="isAdd ? '新增' : '修改'"
|
|
||||||
width="80%"
|
|
||||||
v-model="dialogVisible"
|
|
||||||
@closed="closed"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
>
|
|
||||||
<el-form
|
|
||||||
ref="formRef"
|
|
||||||
label-position="right"
|
|
||||||
label-suffix=":"
|
|
||||||
:model="formField"
|
|
||||||
:rules="formRules"
|
|
||||||
v-loading="loading"
|
|
||||||
label-width="150"
|
|
||||||
>
|
|
||||||
<el-form-item prop="name" label="名称">
|
|
||||||
<el-input placeholder="请输入" v-model="formField.name" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="code" label="字段">
|
|
||||||
<el-input placeholder="请输入" v-model="formField.code" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="contact_name" label="联系人">
|
|
||||||
<el-input placeholder="请输入" v-model="formField.contact_name" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="address" label="地址">
|
|
||||||
<el-input placeholder="请输入" v-model="formField.address" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="enabled" label="是否启用">
|
|
||||||
<el-switch v-model="formField.enabled" active-text="启用" inactive-text="禁用" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button @click="closed"> 取 消 </el-button>
|
|
||||||
<el-button type="primary" :loading="loading" @click="submitForm"> 确 定 </el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="title">厂商管理</div>
|
|
||||||
<el-form
|
|
||||||
ref="searchFormRef"
|
|
||||||
class="form"
|
|
||||||
:inline="true"
|
|
||||||
label-width="98px"
|
|
||||||
:model="queryParams"
|
|
||||||
>
|
|
||||||
<el-form-item label="名称" prop="name">
|
|
||||||
<el-input
|
|
||||||
clearable
|
|
||||||
placeholder="请输入"
|
|
||||||
style="width: 220px"
|
|
||||||
v-model="queryParams.name"
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label-width="100px">
|
|
||||||
<el-button icon="Search" type="primary" @click="handleQuery"> 搜索 </el-button>
|
|
||||||
<el-button icon="RefreshRight" @click="resetQuery"> 重置 </el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<div style="margin-bottom: 20px">
|
|
||||||
<el-button icon="Plus" type="primary" @click="add"> 新建 </el-button>
|
|
||||||
</div>
|
|
||||||
<div ref="tableContainerEl" class="table_border table">
|
|
||||||
<el-table
|
|
||||||
ref="tableRef"
|
|
||||||
:data="tableData"
|
|
||||||
v-loading="loading"
|
|
||||||
header-row-class-name="table-header"
|
|
||||||
>
|
|
||||||
<el-table-column align="center" label="名称" prop="name" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="字段" prop="code" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="联系人" prop="contact_name" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="地址" prop="address" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="是否启用" prop="enabled">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.enabled ? '启用' : '禁用' }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="操作" width="120">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button link type="primary" @click="edit(row)"> 编辑 </el-button>
|
|
||||||
|
|
||||||
<el-button link type="danger" @click="handleDelete(row)"> 删除 </el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<pagination
|
|
||||||
v-show="total > 0"
|
|
||||||
:total="total"
|
|
||||||
v-model:limit="queryParams.limit"
|
|
||||||
v-model:page="queryParams.page"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HandleDialog
|
|
||||||
v-if="isShowDialog"
|
|
||||||
:type="rowType"
|
|
||||||
:row="currentRow"
|
|
||||||
@closed="isShowDialog = false"
|
|
||||||
@on-success="getList"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { useTableRPC } from '@/utils/hook'
|
|
||||||
import HandleDialog from './HandleDialog.vue'
|
|
||||||
|
|
||||||
const rowType = ref('')
|
|
||||||
const currentRow = ref(null)
|
|
||||||
const add = () => {
|
|
||||||
isShowDialog.value = true
|
|
||||||
currentRow.value = null
|
|
||||||
rowType.value = 'add'
|
|
||||||
}
|
|
||||||
const edit = (row) => {
|
|
||||||
isShowDialog.value = true
|
|
||||||
rowType.value = 'edit'
|
|
||||||
currentRow.value = row
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
loading,
|
|
||||||
tableData,
|
|
||||||
tableRef,
|
|
||||||
searchFormRef,
|
|
||||||
total,
|
|
||||||
queryParams,
|
|
||||||
getList,
|
|
||||||
handleQuery,
|
|
||||||
resetQuery,
|
|
||||||
handleDelete,
|
|
||||||
isShowDialog,
|
|
||||||
maxHeight,
|
|
||||||
tableContainerEl,
|
|
||||||
} = useTableRPC({
|
|
||||||
queryParams: {
|
|
||||||
orderby: [{ id: 'desc' }],
|
|
||||||
},
|
|
||||||
remoteDataFn: '/vendor/list',
|
|
||||||
deleteConfig: {
|
|
||||||
remote: '/vendor/delete',
|
|
||||||
key: 'id',
|
|
||||||
message(row) {
|
|
||||||
return `确认删除 “${row.name}”`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
getList()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.title {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Loading…
Reference in new issue