update V3.0.0
This commit is contained in:
1 parent
3b4e0ca614
commit
b16dd30f0b
1227 files changed
+337804
-4
No files matched your search
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<Error type="403" @error-click="push('/')" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'Error403' })
|
||||
|
||||
const { push } = useRouter()
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<Error @error-click="push('/')" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'Error404' })
|
||||
const { push } = useRouter()
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<Error type="500" @error-click="push('/')" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'Error500' })
|
||||
const { push } = useRouter()
|
||||
</script>
|
||||
@@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="divBox">
|
||||
<el-row :gutter="24" class="baseInfo">
|
||||
<el-col :xs="12" :sm="12" :lg="6" class="ivu-mb">
|
||||
<el-card :bordered="false" dis-hover :padding="12">
|
||||
<template #header>
|
||||
<div class="acea-row row-between-wrapper">
|
||||
<span>会员总数</span>
|
||||
<el-tag type="success">全平台</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="content" v-if="count">
|
||||
<span class="content-number spBlock mb15"><count-to :start-val="0" :end-val="count.userCount" :duration="2600" class="card-panel-num" /></span>
|
||||
<el-divider />
|
||||
<div class="acea-row row-between-wrapper">
|
||||
<span class="content-time">今日订单数</span>
|
||||
<span>{{ count.todayCount }} 单</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :lg="6" class="ivu-mb">
|
||||
<el-card :bordered="false" dis-hover :padding="12">
|
||||
<template #header>
|
||||
<div class="acea-row row-between-wrapper">
|
||||
<span>订单总数</span>
|
||||
<el-tag type="success">全平台</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="content" v-if="count">
|
||||
<span class="content-number spBlock mb15"><count-to :start-val="0" :end-val="count.orderCount" :duration="3000" class="card-panel-num" /></span>
|
||||
<el-divider />
|
||||
<div class="acea-row row-between-wrapper">
|
||||
<span class="content-time">昨日订单数</span>
|
||||
<span>{{ count.proCount }} 单</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :lg="6" class="ivu-mb">
|
||||
<el-card :bordered="false" dis-hover :padding="12">
|
||||
<template #header>
|
||||
<div class="acea-row row-between-wrapper">
|
||||
<span>总金额</span>
|
||||
<el-tag type="success">全平台</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="content" v-if="count">
|
||||
<span class="content-number spBlock mb15"><count-to :start-val="0" :end-val="count.priceCount" :duration="3200" class="card-panel-num" /></span>
|
||||
<el-divider />
|
||||
<div class="acea-row row-between-wrapper">
|
||||
<span class="content-time">近七天订单数</span>
|
||||
<span>{{ count.lastWeekCount }} 单</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :lg="6" class="ivu-mb">
|
||||
<el-card :bordered="false" dis-hover :padding="12">
|
||||
<template #header>
|
||||
<div class="acea-row row-between-wrapper">
|
||||
<span>商品总数</span>
|
||||
<el-tag type="success">全平台</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="content" v-if="count">
|
||||
<span class="content-number spBlock mb15"><count-to :start-val="0" :end-val="count.goodsCount" :duration="3600" class="card-panel-num" /></span>
|
||||
<el-divider />
|
||||
<div class="acea-row row-between-wrapper">
|
||||
<span class="content-time">本月订单数</span>
|
||||
<span>{{ count.monthCount }} 单</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<PanelGroupT />
|
||||
<div class="divBox">
|
||||
<el-card shadow="never" class="mt-10px">
|
||||
<template #header>
|
||||
<div class="flex justify-between h-3">
|
||||
<span>{{ t('workplace.notice') }}</span>
|
||||
<el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
|
||||
</div>
|
||||
</template>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
|
||||
<div class="flex items-center">
|
||||
<img :src="avatar" alt="" class="w-35px h-35px rounded-[50%] mr-20px" />
|
||||
<div>
|
||||
<div class="text-14px">
|
||||
{{ item.type == 1 ? '通知' : '公告' }} : {{ item.title }}
|
||||
</div>
|
||||
<div class="mt-15px text-12px text-gray-400">
|
||||
{{ formatTime(item.createTime, 'yyyy-MM-dd') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
</div>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as StoreOrderApi from '@/api/mall/order/storeOrder'
|
||||
import * as NoticeApi from '@/api/system/notice'
|
||||
import PanelGroupT from './PanelGroupT.vue'
|
||||
import { formatTime } from '@/utils'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import avatarImg from '@/assets/imgs/avatar.gif'
|
||||
const userStore = useUserStore()
|
||||
const avatar = userStore.getUser.avatar ? userStore.getUser.avatar : avatarImg
|
||||
const { t } = useI18n()
|
||||
const count = ref({
|
||||
todayPrice: 0,
|
||||
todayCount: 0,
|
||||
proPrice: 0,
|
||||
proCount: 0,
|
||||
monthPrice: 0,
|
||||
monthCount: 0,
|
||||
lastWeekCount: 0,
|
||||
lastWeekPrice: 0,
|
||||
userCount: 0,
|
||||
orderCount: 0,
|
||||
priceCount: 0,
|
||||
goodsCount: 0
|
||||
})
|
||||
const loading = ref(true)
|
||||
const notice = ref([])
|
||||
|
||||
|
||||
/** 查询列表 */
|
||||
const getData = async () => {
|
||||
try {
|
||||
const data = await StoreOrderApi.getShopCount()
|
||||
|
||||
count.value = data
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询公告列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await NoticeApi.getNoticePage({})
|
||||
notice.value = data.list
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getData()
|
||||
getList()
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.acea-row {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-lines: multiple;
|
||||
-moz-box-lines: multiple;
|
||||
-o-box-lines: multiple;
|
||||
-webkit-flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
/* 辅助类 */
|
||||
}
|
||||
|
||||
/* 上下两边居中对齐 */
|
||||
.acea-row.row-between-wrapper {
|
||||
-webkit-box-align: center;
|
||||
-moz-box-align: center;
|
||||
-o-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: justify;
|
||||
-moz-box-pack: justify;
|
||||
-o-box-pack: justify;
|
||||
-ms-flex-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.panel-group {
|
||||
margin-top: 18px;
|
||||
|
||||
.card-panel-col {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.card-panel {
|
||||
height: 108px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: #666;
|
||||
background: #fff;
|
||||
box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
|
||||
border-color: rgba(0, 0, 0, 0.05);
|
||||
|
||||
.icon-people {
|
||||
color: #40c9c6;
|
||||
}
|
||||
|
||||
.icon-message {
|
||||
color: #36a3f7;
|
||||
}
|
||||
|
||||
.icon-money {
|
||||
color: #f4516c;
|
||||
}
|
||||
|
||||
.icon-shopping {
|
||||
color: #34bfa3;
|
||||
}
|
||||
|
||||
.card-panel-icon-wrapper {
|
||||
float: left;
|
||||
margin: 14px 0 0 14px;
|
||||
padding: 16px;
|
||||
transition: all 0.38s ease-out;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.card-panel-icon {
|
||||
float: left;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.card-panel-description {
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
margin: 26px;
|
||||
margin-left: 0px;
|
||||
|
||||
.card-panel-text {
|
||||
line-height: 18px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-panel-num {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 550px) {
|
||||
.card-panel-description {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-panel-icon-wrapper {
|
||||
float: none !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
|
||||
.svg-icon {
|
||||
display: block;
|
||||
margin: 14px auto !important;
|
||||
float: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.baseInfo {
|
||||
::v-deep .el-card__header {
|
||||
padding: 15px 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ivu-mb {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.up,
|
||||
.el-icon-caret-top {
|
||||
color: #f5222d;
|
||||
font-size: 12px;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.down,
|
||||
.el-icon-caret-bottom {
|
||||
color: #39c15b;
|
||||
font-size: 12px;
|
||||
/*opacity: 100% !important;*/
|
||||
}
|
||||
|
||||
.content {
|
||||
&-number {
|
||||
font-size: 30px;
|
||||
}
|
||||
&-time {
|
||||
font-size: 14px;
|
||||
/*color: #8C8C8C;*/
|
||||
}
|
||||
}
|
||||
.spBlock {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<el-row :class="prefixCls" :gutter="20" justify="space-between">
|
||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" :rows="2" animated>
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--peoples p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon :size="40" icon="svg-icon:peoples" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>{{ t('analysis.newUser') }}
|
||||
</div>
|
||||
<CountTo
|
||||
:duration="2600"
|
||||
:end-val="102400"
|
||||
:start-val="0"
|
||||
class="text-right text-20px font-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" :rows="2" animated>
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--message p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon :size="40" icon="svg-icon:message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>{{ t('analysis.unreadInformation') }}
|
||||
</div>
|
||||
<CountTo
|
||||
:duration="2600"
|
||||
:end-val="81212"
|
||||
:start-val="0"
|
||||
class="text-right text-20px font-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" :rows="2" animated>
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon :size="40" icon="svg-icon:money" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>{{ t('analysis.transactionAmount') }}
|
||||
</div>
|
||||
<CountTo
|
||||
:duration="2600"
|
||||
:end-val="9280"
|
||||
:start-val="0"
|
||||
class="text-right text-20px font-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" :rows="2" animated>
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--shopping p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon :size="40" icon="svg-icon:shopping" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>{{ t('analysis.totalShopping') }}
|
||||
</div>
|
||||
<CountTo
|
||||
:duration="2600"
|
||||
:end-val="13600"
|
||||
:start-val="0"
|
||||
class="text-right text-20px font-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" justify="space-between">
|
||||
<el-col :lg="10" :md="24" :sm="24" :xl="10" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="300" :options="pieOptionsData" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :lg="14" :md="24" :sm="24" :xl="14" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="300" :options="barOptionsData" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" :rows="4" animated>
|
||||
<Echart :height="350" :options="lineOptionsData" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { set } from 'lodash-es'
|
||||
import { EChartsOption } from 'echarts'
|
||||
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import type { AnalysisTotalTypes } from './types'
|
||||
import { barOptions, lineOptions, pieOptions } from './echarts-data'
|
||||
|
||||
defineOptions({ name: 'Home2' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('panel')
|
||||
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
||||
|
||||
let totalState = reactive<AnalysisTotalTypes>({
|
||||
users: 0,
|
||||
messages: 0,
|
||||
moneys: 0,
|
||||
shoppings: 0
|
||||
})
|
||||
|
||||
const getCount = async () => {
|
||||
const data = {
|
||||
users: 102400,
|
||||
messages: 81212,
|
||||
moneys: 9280,
|
||||
shoppings: 13600
|
||||
}
|
||||
totalState = Object.assign(totalState, data)
|
||||
}
|
||||
|
||||
// 用户来源
|
||||
const getUserAccessSource = async () => {
|
||||
const data = [
|
||||
{ value: 335, name: 'analysis.directAccess' },
|
||||
{ value: 310, name: 'analysis.mailMarketing' },
|
||||
{ value: 234, name: 'analysis.allianceAdvertising' },
|
||||
{ value: 135, name: 'analysis.videoAdvertising' },
|
||||
{ value: 1548, name: 'analysis.searchEngines' }
|
||||
]
|
||||
set(
|
||||
pieOptionsData,
|
||||
'legend.data',
|
||||
data.map((v) => t(v.name))
|
||||
)
|
||||
set(pieOptionsData, 'series.data', data)
|
||||
}
|
||||
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
|
||||
|
||||
// 周活跃量
|
||||
const getWeeklyUserActivity = async () => {
|
||||
const data = [
|
||||
{ value: 13253, name: 'analysis.monday' },
|
||||
{ value: 34235, name: 'analysis.tuesday' },
|
||||
{ value: 26321, name: 'analysis.wednesday' },
|
||||
{ value: 12340, name: 'analysis.thursday' },
|
||||
{ value: 24643, name: 'analysis.friday' },
|
||||
{ value: 1322, name: 'analysis.saturday' },
|
||||
{ value: 1324, name: 'analysis.sunday' }
|
||||
]
|
||||
set(
|
||||
barOptionsData,
|
||||
'xAxis.data',
|
||||
data.map((v) => t(v.name))
|
||||
)
|
||||
set(barOptionsData, 'series', [
|
||||
{
|
||||
name: t('analysis.activeQuantity'),
|
||||
data: data.map((v) => v.value),
|
||||
type: 'bar'
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
const lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption
|
||||
|
||||
// 每月销售总额
|
||||
const getMonthlySales = async () => {
|
||||
const data = [
|
||||
{ estimate: 100, actual: 120, name: 'analysis.january' },
|
||||
{ estimate: 120, actual: 82, name: 'analysis.february' },
|
||||
{ estimate: 161, actual: 91, name: 'analysis.march' },
|
||||
{ estimate: 134, actual: 154, name: 'analysis.april' },
|
||||
{ estimate: 105, actual: 162, name: 'analysis.may' },
|
||||
{ estimate: 160, actual: 140, name: 'analysis.june' },
|
||||
{ estimate: 165, actual: 145, name: 'analysis.july' },
|
||||
{ estimate: 114, actual: 250, name: 'analysis.august' },
|
||||
{ estimate: 163, actual: 134, name: 'analysis.september' },
|
||||
{ estimate: 185, actual: 56, name: 'analysis.october' },
|
||||
{ estimate: 118, actual: 99, name: 'analysis.november' },
|
||||
{ estimate: 123, actual: 123, name: 'analysis.december' }
|
||||
]
|
||||
set(
|
||||
lineOptionsData,
|
||||
'xAxis.data',
|
||||
data.map((v) => t(v.name))
|
||||
)
|
||||
set(lineOptionsData, 'series', [
|
||||
{
|
||||
name: t('analysis.estimate'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: data.map((v) => v.estimate),
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: t('analysis.actual'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {},
|
||||
data: data.map((v) => v.actual),
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
const getAllApi = async () => {
|
||||
await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
getAllApi()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$prefix-cls: #{$namespace}-panel;
|
||||
|
||||
.#{$prefix-cls} {
|
||||
&__item {
|
||||
&--peoples {
|
||||
color: #40c9c6;
|
||||
}
|
||||
|
||||
&--message {
|
||||
color: #36a3f7;
|
||||
}
|
||||
|
||||
&--money {
|
||||
color: #f4516c;
|
||||
}
|
||||
|
||||
&--shopping {
|
||||
color: #34bfa3;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
:deep(.#{$namespace}-icon) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__item--icon {
|
||||
transition: all 0.38s ease-out;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__item--peoples {
|
||||
background: #40c9c6;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__item--message {
|
||||
background: #36a3f7;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__item--money {
|
||||
background: #f4516c;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__item--shopping {
|
||||
background: #34bfa3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div class="divBox" v-show="isShow">
|
||||
<el-row :gutter="24" class="dashboard-console-grid">
|
||||
<el-col v-bind="grid" class="ivu-mb">
|
||||
<el-card :bordered="false">
|
||||
<router-link :to="{ path: '/product/store-product' }">
|
||||
<Icon icon="ep:goods" style="color: #69c0ff" :size="35" />
|
||||
<p>商品管理</p>
|
||||
</router-link>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col v-bind="grid" class="ivu-mb">
|
||||
<el-card :bordered="false">
|
||||
<router-link :to="{ path: '/member/user' }">
|
||||
<Icon icon="ep:user" style="color: #95de64" :size="35" />
|
||||
<p>会员管理</p>
|
||||
</router-link>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col v-bind="grid" class="ivu-mb">
|
||||
<el-card :bordered="false">
|
||||
<router-link :to="{ path: '/order/store-order' }">
|
||||
<Icon icon="ep:list" style="color: #ff9c6e" :size="35" />
|
||||
<p>订单管理</p>
|
||||
</router-link>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col v-bind="grid" class="ivu-mb">
|
||||
<el-card :bordered="false">
|
||||
<router-link :to="{ path: '/mall/coupons' }">
|
||||
<Icon icon="ep:ticket" style="color: #b37feb" :size="35" />
|
||||
<p>优惠券管理</p>
|
||||
</router-link>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col v-bind="grid" class="ivu-mb">
|
||||
<el-card :bordered="false">
|
||||
<router-link :to="{ path: '/shop/web-print' }">
|
||||
<Icon icon="ep:postcard" style="color: #ffd666" :size="35" />
|
||||
<p>打印机管理</p>
|
||||
</router-link>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col v-bind="grid" class="ivu-mb">
|
||||
<el-card :bordered="false">
|
||||
<router-link :to="{ path: '/shop/shop' }" >
|
||||
<Icon icon="ep:notebook" style="color: #5cdbd3" :size="35" />
|
||||
<p>门店管理</p>
|
||||
</router-link>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col v-bind="grid" class="ivu-mb">
|
||||
<el-card :bordered="false">
|
||||
<router-link :to="{ path: '/pay/merchant-details' }">
|
||||
<Icon icon="ep:money" style="color: #ff85c0" :size="35" />
|
||||
<p>支付管理</p>
|
||||
</router-link>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col v-bind="grid" class="ivu-mb">
|
||||
<el-card :bordered="false">
|
||||
<router-link :to="{ path: '/message/wechat-templateo' }">
|
||||
<Icon icon="ep:message" style="color: #ffc069" :size="35" />
|
||||
<p>消息管理</p>
|
||||
</router-link>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
|
||||
const grid =ref({
|
||||
xl: 3,
|
||||
lg: 3,
|
||||
md: 6,
|
||||
sm: 8,
|
||||
xs: 8,
|
||||
})
|
||||
|
||||
|
||||
|
||||
const user = wsCache.get(CACHE_KEY.USER)
|
||||
|
||||
const isShow = ref(true)
|
||||
if(user.user.shopId > 0) {
|
||||
isShow.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.panel-group {
|
||||
margin-top: 18px;
|
||||
.card-panel-col{
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.card-panel {
|
||||
height: 108px;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
background: #fff;
|
||||
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
|
||||
border-color: rgba(0, 0, 0, .05);
|
||||
|
||||
|
||||
|
||||
.icon-people {
|
||||
color: #40c9c6;
|
||||
}
|
||||
.icon-message {
|
||||
color: #36a3f7;
|
||||
}
|
||||
.icon-money {
|
||||
color: #f4516c;
|
||||
}
|
||||
.icon-shopping {
|
||||
color: #34bfa3
|
||||
}
|
||||
.card-panel-icon-wrapper {
|
||||
float: left;
|
||||
margin: 14px 0 0 14px;
|
||||
padding: 16px;
|
||||
transition: all 0.38s ease-out;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.card-panel-icon {
|
||||
float: left;
|
||||
font-size: 48px;
|
||||
}
|
||||
.card-panel-description {
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
margin: 26px;
|
||||
margin-left: 0px;
|
||||
.card-panel-text {
|
||||
line-height: 18px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.card-panel-num {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ivu-mb {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.divBox {
|
||||
// padding: 0 20px !important;
|
||||
}
|
||||
|
||||
.dashboard-console-grid {
|
||||
text-align: center;
|
||||
.ivu-card-body {
|
||||
padding: 0;
|
||||
}
|
||||
i {
|
||||
font-size: 32px;
|
||||
}
|
||||
a {
|
||||
display: block;
|
||||
color: inherit;
|
||||
}
|
||||
p {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,308 @@
|
||||
import { EChartsOption } from 'echarts'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
export const lineOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.monthlySales'),
|
||||
left: 'center'
|
||||
},
|
||||
xAxis: {
|
||||
data: [
|
||||
t('analysis.january'),
|
||||
t('analysis.february'),
|
||||
t('analysis.march'),
|
||||
t('analysis.april'),
|
||||
t('analysis.may'),
|
||||
t('analysis.june'),
|
||||
t('analysis.july'),
|
||||
t('analysis.august'),
|
||||
t('analysis.september'),
|
||||
t('analysis.october'),
|
||||
t('analysis.november'),
|
||||
t('analysis.december')
|
||||
],
|
||||
boundaryGap: false,
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
top: 80,
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
},
|
||||
padding: [5, 10]
|
||||
},
|
||||
yAxis: {
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: [t('analysis.estimate'), t('analysis.actual')],
|
||||
top: 50
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.estimate'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: [100, 120, 161, 134, 105, 160, 165, 114, 163, 185, 118, 123],
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: t('analysis.actual'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {},
|
||||
data: [120, 82, 91, 154, 162, 140, 145, 250, 134, 56, 99, 123],
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const pieOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.userAccessSource'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
data: [
|
||||
t('analysis.directAccess'),
|
||||
t('analysis.mailMarketing'),
|
||||
t('analysis.allianceAdvertising'),
|
||||
t('analysis.videoAdvertising'),
|
||||
t('analysis.searchEngines')
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.userAccessSource'),
|
||||
type: 'pie',
|
||||
radius: '55%',
|
||||
center: ['50%', '60%'],
|
||||
data: [
|
||||
{ value: 335, name: t('analysis.directAccess') },
|
||||
{ value: 310, name: t('analysis.mailMarketing') },
|
||||
{ value: 234, name: t('analysis.allianceAdvertising') },
|
||||
{ value: 135, name: t('analysis.videoAdvertising') },
|
||||
{ value: 1548, name: t('analysis.searchEngines') }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const barOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.weeklyUserActivity'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [
|
||||
t('analysis.monday'),
|
||||
t('analysis.tuesday'),
|
||||
t('analysis.wednesday'),
|
||||
t('analysis.thursday'),
|
||||
t('analysis.friday'),
|
||||
t('analysis.saturday'),
|
||||
t('analysis.sunday')
|
||||
],
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.activeQuantity'),
|
||||
data: [13253, 34235, 26321, 12340, 24643, 1322, 1324],
|
||||
type: 'bar'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const radarOption: EChartsOption = {
|
||||
legend: {
|
||||
data: [t('workplace.personal'), t('workplace.team')]
|
||||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
indicator: [
|
||||
{ name: t('workplace.quote'), max: 65 },
|
||||
{ name: t('workplace.contribution'), max: 160 },
|
||||
{ name: t('workplace.hot'), max: 300 },
|
||||
{ name: t('workplace.yield'), max: 130 },
|
||||
{ name: t('workplace.follow'), max: 100 }
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: `xxx${t('workplace.index')}`,
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: [42, 30, 20, 35, 80],
|
||||
name: t('workplace.personal')
|
||||
},
|
||||
{
|
||||
value: [50, 140, 290, 100, 90],
|
||||
name: t('workplace.team')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const wordOptions = {
|
||||
series: [
|
||||
{
|
||||
type: 'wordCloud',
|
||||
gridSize: 2,
|
||||
sizeRange: [12, 50],
|
||||
rotationRange: [-90, 90],
|
||||
shape: 'pentagon',
|
||||
width: 600,
|
||||
height: 400,
|
||||
drawOutOfBound: true,
|
||||
textStyle: {
|
||||
color: function () {
|
||||
return (
|
||||
'rgb(' +
|
||||
[
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160)
|
||||
].join(',') +
|
||||
')'
|
||||
)
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowColor: '#333'
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: 'Sam S Club',
|
||||
value: 10000,
|
||||
textStyle: {
|
||||
color: 'black'
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
color: 'red'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Macys',
|
||||
value: 6181
|
||||
},
|
||||
{
|
||||
name: 'Amy Schumer',
|
||||
value: 4386
|
||||
},
|
||||
{
|
||||
name: 'Jurassic World',
|
||||
value: 4055
|
||||
},
|
||||
{
|
||||
name: 'Charter Communications',
|
||||
value: 2467
|
||||
},
|
||||
{
|
||||
name: 'Chick Fil A',
|
||||
value: 2244
|
||||
},
|
||||
{
|
||||
name: 'Planet Fitness',
|
||||
value: 1898
|
||||
},
|
||||
{
|
||||
name: 'Pitch Perfect',
|
||||
value: 1484
|
||||
},
|
||||
{
|
||||
name: 'Express',
|
||||
value: 1112
|
||||
},
|
||||
{
|
||||
name: 'Home',
|
||||
value: 965
|
||||
},
|
||||
{
|
||||
name: 'Johnny Depp',
|
||||
value: 847
|
||||
},
|
||||
{
|
||||
name: 'Lena Dunham',
|
||||
value: 582
|
||||
},
|
||||
{
|
||||
name: 'Lewis Hamilton',
|
||||
value: 555
|
||||
},
|
||||
{
|
||||
name: 'KXAN',
|
||||
value: 550
|
||||
},
|
||||
{
|
||||
name: 'Mary Ellen Mark',
|
||||
value: 462
|
||||
},
|
||||
{
|
||||
name: 'Farrah Abraham',
|
||||
value: 366
|
||||
},
|
||||
{
|
||||
name: 'Rita Ora',
|
||||
value: 360
|
||||
},
|
||||
{
|
||||
name: 'Serena Williams',
|
||||
value: 282
|
||||
},
|
||||
{
|
||||
name: 'NCAA baseball tournament',
|
||||
value: 273
|
||||
},
|
||||
{
|
||||
name: 'Point Break',
|
||||
value: 265
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
export type WorkplaceTotal = {
|
||||
project: number
|
||||
access: number
|
||||
todo: number
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
name: string
|
||||
icon: string
|
||||
message: string
|
||||
personal: string
|
||||
time: Date | number | string
|
||||
}
|
||||
|
||||
export type Notice = {
|
||||
title: string
|
||||
type: string
|
||||
keys: string[]
|
||||
date: Date | number | string
|
||||
}
|
||||
|
||||
export type Shortcut = {
|
||||
name: string
|
||||
icon: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export type RadarData = {
|
||||
personal: number
|
||||
team: number
|
||||
max: number
|
||||
name: string
|
||||
}
|
||||
export type AnalysisTotalTypes = {
|
||||
users: number
|
||||
messages: number
|
||||
moneys: number
|
||||
shoppings: number
|
||||
}
|
||||
|
||||
export type UserAccessSource = {
|
||||
value: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type WeeklyUserActivity = {
|
||||
value: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type MonthlySales = {
|
||||
name: string
|
||||
estimate: number
|
||||
actual: number
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div
|
||||
:class="prefixCls"
|
||||
class="h-[100%] relative <xl:bg-v-dark <sm:px-10px <xl:px-10px <md:px-10px"
|
||||
>
|
||||
<div class="relative h-full flex mx-auto">
|
||||
<div
|
||||
:class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px <xl:hidden`"
|
||||
>
|
||||
<!-- 左上角的 logo + 系统标题 -->
|
||||
<div class="flex items-center relative text-white">
|
||||
<img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
|
||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||
</div>
|
||||
<!-- 左边的背景图 + 欢迎语 -->
|
||||
<div class="flex justify-center items-center h-[calc(100%-60px)]">
|
||||
<TransitionGroup
|
||||
appear
|
||||
enter-active-class="animate__animated animate__bounceInLeft"
|
||||
tag="div"
|
||||
>
|
||||
<img key="1" alt="" class="w-500px" src="@/assets/imgs/login_bg.png" />
|
||||
<div key="2" class="text-3xl text-white" style="text-align: center;">{{ t('login.welcome') }}</div>
|
||||
<!-- <div key="3" class="mt-5 font-normal text-white text-14px">
|
||||
{{ t('login.message') }}
|
||||
</div> -->
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 p-30px <sm:p-10px dark:bg-v-dark relative">
|
||||
<!-- 右上角的主题、语言选择 -->
|
||||
<div class="flex justify-between items-center text-white @2xl:justify-end @xl:justify-end">
|
||||
<div class="flex items-center @2xl:hidden @xl:hidden">
|
||||
<img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
|
||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-end items-center space-x-10px">
|
||||
<!-- <ThemeSwitch /> -->
|
||||
<!-- <LocaleDropdown class="<xl:text-white dark:text-white" /> -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右边的登录界面 -->
|
||||
<Transition appear enter-active-class="animate__animated animate__bounceInRight">
|
||||
|
||||
<div
|
||||
class="h-full flex items-center m-auto w-[100%] @2xl:max-w-500px @xl:max-w-500px @md:max-w-500px @lg:max-w-500px"
|
||||
>
|
||||
<!-- 账号登录 -->
|
||||
<LoginForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
|
||||
<!-- 手机登录 -->
|
||||
<!-- <MobileForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
|
||||
二维码登录 -->
|
||||
<!-- <QrCodeForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
|
||||
注册 -->
|
||||
<!-- <RegisterForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" /> -->
|
||||
<!-- 三方登录 -->
|
||||
<!-- <SSOLoginVue class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" /> -->
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="Login" setup>
|
||||
import { underlineToHump } from '@/utils'
|
||||
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
//import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||
//import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
|
||||
import { LoginForm } from './components'
|
||||
// import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('login')
|
||||
appStore.setIsDark(false)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$prefix-cls: #{$namespace}-login;
|
||||
|
||||
.#{$prefix-cls} {
|
||||
&__left {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('@/assets/svgs/login-bg.svg');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,343 @@
|
||||
<template>
|
||||
<div
|
||||
:class="prefixCls"
|
||||
class="relative h-[100%] lt-xl:bg-[var(--login-bg-color)] lt-md:px-10px lt-sm:px-10px lt-xl:px-10px"
|
||||
>
|
||||
<div class="relative mx-auto h-full flex">
|
||||
<div
|
||||
:class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden`"
|
||||
>
|
||||
<!-- 左上角的 logo + 系统标题 -->
|
||||
<div class="relative flex items-center text-white">
|
||||
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
|
||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||
</div>
|
||||
<!-- 左边的背景图 + 欢迎语 -->
|
||||
<div class="h-[calc(100%-60px)] flex items-center justify-center">
|
||||
<TransitionGroup
|
||||
appear
|
||||
enter-active-class="animate__animated animate__bounceInLeft"
|
||||
tag="div"
|
||||
>
|
||||
<img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" />
|
||||
<div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div>
|
||||
<div key="3" class="mt-5 text-14px font-normal text-white">
|
||||
{{ t('login.message') }}
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex-1 p-30px dark:bg-[var(--login-bg-color)] lt-sm:p-10px">
|
||||
<!-- 右上角的主题、语言选择 -->
|
||||
<div
|
||||
class="flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end"
|
||||
>
|
||||
<div class="flex items-center at-2xl:hidden at-xl:hidden">
|
||||
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
|
||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-end space-x-10px">
|
||||
<ThemeSwitch />
|
||||
<LocaleDropdown class="dark:text-white lt-xl:text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右边的登录界面 -->
|
||||
<Transition appear enter-active-class="animate__animated animate__bounceInRight">
|
||||
<div
|
||||
class="m-auto h-full w-[100%] flex items-center at-2xl:max-w-500px at-lg:max-w-500px at-md:max-w-500px at-xl:max-w-500px"
|
||||
>
|
||||
<!-- 账号登录 -->
|
||||
<el-form
|
||||
v-show="getShow"
|
||||
ref="formLogin"
|
||||
:model="loginData.loginForm"
|
||||
:rules="LoginRules"
|
||||
class="login-form"
|
||||
label-position="top"
|
||||
label-width="120px"
|
||||
size="large"
|
||||
>
|
||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item v-if="loginData.tenantEnable" prop="tenantName">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
link
|
||||
type="primary"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.username"
|
||||
:placeholder="t('login.usernamePlaceholder')"
|
||||
:prefix-icon="iconAvatar"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.password"
|
||||
:placeholder="t('login.passwordPlaceholder')"
|
||||
:prefix-icon="iconLock"
|
||||
show-password
|
||||
type="password"
|
||||
@keyup.enter="getCode()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="24"
|
||||
style="
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
margin-top: -20px;
|
||||
margin-bottom: -20px;
|
||||
"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-row justify="space-between" style="width: 100%">
|
||||
<el-col :span="6">
|
||||
<el-checkbox v-model="loginData.loginForm.rememberMe">
|
||||
{{ t('login.remember') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col :offset="6" :span="12">
|
||||
<el-link style="float: right" type="primary">{{
|
||||
t('login.forgetPassword')
|
||||
}}</el-link>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
:title="t('login.login')"
|
||||
class="w-[100%]"
|
||||
type="primary"
|
||||
@click="getCode()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
ref="verify"
|
||||
:captchaType="captchaType"
|
||||
:imgSize="{ width: '400px', height: '200px' }"
|
||||
mode="pop"
|
||||
@success="handleLogin"
|
||||
/>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { underlineToHump } from '@/utils'
|
||||
|
||||
import { ElLoading } from 'element-plus'
|
||||
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
|
||||
import * as LoginApi from '@/api/login'
|
||||
import * as authUtil from '@/utils/auth'
|
||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
import { LoginStateEnum, useFormValid, useLoginState } from './components/useLogin'
|
||||
import LoginFormTitle from './components/LoginFormTitle.vue'
|
||||
import router from '@/router'
|
||||
|
||||
defineOptions({ name: 'SocialLogin' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
|
||||
const appStore = useAppStore()
|
||||
const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('login')
|
||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||
const iconLock = useIcon({ icon: 'ep:lock' })
|
||||
const formLogin = ref<any>()
|
||||
const { validForm } = useFormValid(formLogin)
|
||||
const { getLoginState } = useLoginState()
|
||||
const { push } = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
const loginLoading = ref(false)
|
||||
const verify = ref()
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||
|
||||
const LoginRules = {
|
||||
tenantName: [required],
|
||||
username: [required],
|
||||
password: [required]
|
||||
}
|
||||
const loginData = reactive({
|
||||
isShowPassword: false,
|
||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE !== 'false',
|
||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE !== 'false',
|
||||
loginForm: {
|
||||
tenantName: '芋道源码',
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
captchaVerification: '',
|
||||
rememberMe: false
|
||||
}
|
||||
})
|
||||
|
||||
// 获取验证码
|
||||
const getCode = async () => {
|
||||
// 情况一,未开启:则直接登录
|
||||
if (!loginData.captchaEnable) {
|
||||
await handleLogin({})
|
||||
} else {
|
||||
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
|
||||
// 弹出验证码
|
||||
verify.value.show()
|
||||
}
|
||||
}
|
||||
//获取租户ID
|
||||
const getTenantId = async () => {
|
||||
if (loginData.tenantEnable) {
|
||||
const res = await LoginApi.getTenantIdByName(loginData.loginForm.tenantName)
|
||||
authUtil.setTenantId(res)
|
||||
}
|
||||
}
|
||||
// 记住我
|
||||
const getCookie = () => {
|
||||
const loginForm = authUtil.getLoginForm()
|
||||
if (loginForm) {
|
||||
loginData.loginForm = {
|
||||
...loginData.loginForm,
|
||||
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
|
||||
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
|
||||
rememberMe: loginForm.rememberMe ? true : false,
|
||||
tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName
|
||||
}
|
||||
}
|
||||
}
|
||||
const loading = ref() // ElLoading.service 返回的实例
|
||||
|
||||
// tricky: 配合LoginForm.vue中redirectUri需要对参数进行encode,需要在回调后进行decode
|
||||
function getUrlValue(key: string): string {
|
||||
const url = new URL(decodeURIComponent(location.href))
|
||||
return url.searchParams.get(key) ?? ''
|
||||
}
|
||||
|
||||
// 尝试登录: 当账号已经绑定,socialLogin会直接获得token
|
||||
const tryLogin = async () => {
|
||||
try {
|
||||
const type = getUrlValue('type')
|
||||
const redirect = getUrlValue('redirect')
|
||||
const code = route?.query?.code as string
|
||||
const state = route?.query?.state as string
|
||||
|
||||
const res = await LoginApi.socialLogin(type, code, state)
|
||||
authUtil.setToken(res)
|
||||
|
||||
router.push({ path: redirect || '/' })
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
// 登录
|
||||
const handleLogin = async (params) => {
|
||||
loginLoading.value = true
|
||||
try {
|
||||
await getTenantId()
|
||||
const data = await validForm()
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
|
||||
let redirect = getUrlValue('redirect')
|
||||
|
||||
const type = getUrlValue('type')
|
||||
const code = route?.query?.code as string
|
||||
const state = route?.query?.state as string
|
||||
|
||||
const res = await LoginApi.login({
|
||||
// 账号密码登录
|
||||
username: loginData.loginForm.username,
|
||||
password: loginData.loginForm.password,
|
||||
captchaVerification: params.captchaVerification,
|
||||
// 社交登录
|
||||
socialCode: code,
|
||||
socialState: state,
|
||||
socialType: type
|
||||
})
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
loading.value = ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在加载系统中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
if (loginData.loginForm.rememberMe) {
|
||||
authUtil.setLoginForm(loginData.loginForm)
|
||||
} else {
|
||||
authUtil.removeLoginForm()
|
||||
}
|
||||
authUtil.setToken(res)
|
||||
if (!redirect) {
|
||||
redirect = '/'
|
||||
}
|
||||
// 判断是否为SSO登录
|
||||
if (redirect.indexOf('sso') !== -1) {
|
||||
window.location.href = window.location.href.replace('/login?redirect=', '')
|
||||
} else {
|
||||
push({ path: redirect || permissionStore.addRouters[0].path })
|
||||
}
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
loading.value.close()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCookie()
|
||||
tryLogin()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$prefix-cls: #{$namespace}-login;
|
||||
|
||||
.#{$prefix-cls} {
|
||||
overflow: auto;
|
||||
|
||||
&__left {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('@/assets/svgs/login-bg.svg');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<el-form
|
||||
v-show="getShow"
|
||||
ref="formLogin"
|
||||
:model="loginData.loginForm"
|
||||
:rules="LoginRules"
|
||||
class="login-form"
|
||||
label-position="top"
|
||||
label-width="120px"
|
||||
size="large"
|
||||
>
|
||||
<el-row style="maring-left: -10px; maring-right: -10px">
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
type="primary"
|
||||
link
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.username"
|
||||
:placeholder="t('login.usernamePlaceholder')"
|
||||
:prefix-icon="iconAvatar"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.password"
|
||||
:placeholder="t('login.passwordPlaceholder')"
|
||||
:prefix-icon="iconLock"
|
||||
show-password
|
||||
type="password"
|
||||
@keyup.enter="getCode()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="24"
|
||||
style="padding-left: 10px; padding-right: 10px; margin-top: -20px; margin-bottom: -20px"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-row justify="space-between" style="width: 100%">
|
||||
<el-col :span="6">
|
||||
<el-checkbox v-model="loginData.loginForm.rememberMe">
|
||||
{{ t('login.remember') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
:title="t('login.login')"
|
||||
class="w-[100%]"
|
||||
type="primary"
|
||||
@click="getCode()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
ref="verify"
|
||||
:captchaType="captchaType"
|
||||
:imgSize="{ width: '400px', height: '200px' }"
|
||||
mode="pop"
|
||||
@success="handleLogin"
|
||||
/>
|
||||
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" name="LoginForm" setup>
|
||||
import { ElLoading } from 'element-plus'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
|
||||
import * as authUtil from '@/utils/auth'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import * as LoginApi from '@/api/login'
|
||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||
|
||||
const { t } = useI18n()
|
||||
//const message = useMessage()
|
||||
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||
const iconLock = useIcon({ icon: 'ep:lock' })
|
||||
const formLogin = ref()
|
||||
const { validForm } = useFormValid(formLogin)
|
||||
const { getLoginState } = useLoginState()
|
||||
const { currentRoute, push } = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
const redirect = ref<string>('')
|
||||
const loginLoading = ref(false)
|
||||
const verify = ref()
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||
|
||||
const LoginRules = {
|
||||
tenantName: [required],
|
||||
username: [required],
|
||||
password: [required]
|
||||
}
|
||||
const loginData = reactive({
|
||||
isShowPassword: false,
|
||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||
loginForm: {
|
||||
tenantName: 'yshop',
|
||||
//username: 'yshop002',
|
||||
//password: '123456789',
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
captchaVerification: '',
|
||||
rememberMe: false
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
// 获取验证码
|
||||
const getCode = async () => {
|
||||
// 情况一,未开启:则直接登录
|
||||
console.log('oginData.captchaEnable:',loginData.captchaEnable)
|
||||
if (loginData.captchaEnable === 'false') {
|
||||
await handleLogin({})
|
||||
} else {
|
||||
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
|
||||
// 弹出验证码
|
||||
verify.value.show()
|
||||
}
|
||||
}
|
||||
//获取租户ID
|
||||
const getTenantId = async () => {
|
||||
if (loginData.tenantEnable === 'true') {
|
||||
const res = await LoginApi.getTenantIdByName(loginData.loginForm.tenantName)
|
||||
authUtil.setTenantId(res)
|
||||
}
|
||||
}
|
||||
// 记住我
|
||||
const getCookie = () => {
|
||||
const loginForm = authUtil.getLoginForm()
|
||||
if (loginForm) {
|
||||
loginData.loginForm = {
|
||||
...loginData.loginForm,
|
||||
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
|
||||
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
|
||||
rememberMe: loginForm.rememberMe ? true : false,
|
||||
tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName
|
||||
}
|
||||
}
|
||||
}
|
||||
// 登录
|
||||
const handleLogin = async (params) => {
|
||||
loginLoading.value = true
|
||||
try {
|
||||
await getTenantId()
|
||||
const data = await validForm()
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
loginData.loginForm.captchaVerification = params.captchaVerification
|
||||
const res = await LoginApi.login(loginData.loginForm)
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在加载系统中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
if (loginData.loginForm.rememberMe) {
|
||||
authUtil.setLoginForm(loginData.loginForm)
|
||||
} else {
|
||||
authUtil.removeLoginForm()
|
||||
}
|
||||
authUtil.setToken(res)
|
||||
if (!redirect.value) {
|
||||
redirect.value = '/'
|
||||
}
|
||||
// 判断是否为SSO登录
|
||||
if (redirect.value.indexOf('sso') !== -1) {
|
||||
window.location.href = window.location.href.replace('/login?redirect=', '')
|
||||
} else {
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
}
|
||||
} catch {
|
||||
loginLoading.value = false
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
const loadingInstance = ElLoading.service()
|
||||
loadingInstance.close()
|
||||
}, 400)
|
||||
}
|
||||
}
|
||||
|
||||
// 社交登录
|
||||
// const doSocialLogin = async (type: number) => {
|
||||
// if (type === 0) {
|
||||
// message.error('此方式未配置')
|
||||
// } else {
|
||||
// loginLoading.value = true
|
||||
// if (loginData.tenantEnable === 'true') {
|
||||
// await message.prompt('请输入租户名称', t('common.reminder')).then(async ({ value }) => {
|
||||
// const res = await LoginApi.getTenantIdByName(value)
|
||||
// authUtil.setTenantId(res)
|
||||
// })
|
||||
// }
|
||||
// // 计算 redirectUri
|
||||
// const redirectUri =
|
||||
// location.origin + '/social-login?type=' + type + '&redirect=' + (redirect.value || '/')
|
||||
// // 进行跳转
|
||||
// const res = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
|
||||
// window.location.href = res
|
||||
// }
|
||||
// }
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
redirect.value = route?.query?.redirect as string
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
onMounted(() => {
|
||||
getCookie()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.anticon) {
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.login-code {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
float: right;
|
||||
|
||||
img {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
max-width: 100px;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<h2 class="enter-x mb-3 text-center text-2xl font-bold xl:text-center xl:text-3xl">
|
||||
{{ getFormTitle }}
|
||||
</h2>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||
|
||||
defineOptions({ name: 'LoginFormTitle' })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getLoginState } = useLoginState()
|
||||
|
||||
const getFormTitle = computed(() => {
|
||||
const titleObj = {
|
||||
[LoginStateEnum.RESET_PASSWORD]: t('sys.login.forgetFormTitle'),
|
||||
[LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'),
|
||||
[LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'),
|
||||
[LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'),
|
||||
[LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'),
|
||||
[LoginStateEnum.SSO]: t('sys.login.ssoFormTitle')
|
||||
}
|
||||
return titleObj[unref(getLoginState)]
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<el-form
|
||||
v-show="getShow"
|
||||
ref="formSmsLogin"
|
||||
:model="loginData.loginForm"
|
||||
:rules="rules"
|
||||
class="login-form"
|
||||
label-position="top"
|
||||
label-width="120px"
|
||||
size="large"
|
||||
>
|
||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||
<!-- 租户名 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
type="primary"
|
||||
link
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 手机号 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="mobileNumber">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.mobileNumber"
|
||||
:placeholder="t('login.mobileNumberPlaceholder')"
|
||||
:prefix-icon="iconCellphone"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 验证码 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="code">
|
||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||
<el-col :span="24">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.code"
|
||||
:placeholder="t('login.codePlaceholder')"
|
||||
:prefix-icon="iconCircleCheck"
|
||||
>
|
||||
<!-- <el-button class="w-[100%]"> -->
|
||||
<template #append>
|
||||
<span
|
||||
v-if="mobileCodeTimer <= 0"
|
||||
class="getMobileCode"
|
||||
style="cursor: pointer"
|
||||
@click="getSmsCode"
|
||||
>
|
||||
{{ t('login.getSmsCode') }}
|
||||
</span>
|
||||
<span v-if="mobileCodeTimer > 0" class="getMobileCode" style="cursor: pointer">
|
||||
{{ mobileCodeTimer }}秒后可重新获取
|
||||
</span>
|
||||
</template>
|
||||
</el-input>
|
||||
<!-- </el-button> -->
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 登录按钮 / 返回按钮 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
:title="t('login.login')"
|
||||
class="w-[100%]"
|
||||
type="primary"
|
||||
@click="signIn()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
:title="t('login.backLogin')"
|
||||
class="w-[100%]"
|
||||
@click="handleBackLogin()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
|
||||
import { setTenantId, setToken } from '@/utils/auth'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { getTenantIdByName, sendSmsCode, smsLogin } from '@/api/login'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||
import { ElLoading } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'MobileForm' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const permissionStore = usePermissionStore()
|
||||
const { currentRoute, push } = useRouter()
|
||||
const formSmsLogin = ref()
|
||||
const loginLoading = ref(false)
|
||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||
const iconCellphone = useIcon({ icon: 'ep:cellphone' })
|
||||
const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
|
||||
const { validForm } = useFormValid(formSmsLogin)
|
||||
const { handleBackLogin, getLoginState } = useLoginState()
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.MOBILE)
|
||||
|
||||
const rules = {
|
||||
tenantName: [required],
|
||||
mobileNumber: [required],
|
||||
code: [required]
|
||||
}
|
||||
const loginData = reactive({
|
||||
codeImg: '',
|
||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||
token: '',
|
||||
loading: {
|
||||
signIn: false
|
||||
},
|
||||
loginForm: {
|
||||
uuid: '',
|
||||
tenantName: '芋道源码',
|
||||
mobileNumber: '',
|
||||
code: ''
|
||||
}
|
||||
})
|
||||
const smsVO = reactive({
|
||||
smsCode: {
|
||||
mobile: '',
|
||||
scene: 21
|
||||
},
|
||||
loginSms: {
|
||||
mobile: '',
|
||||
code: ''
|
||||
}
|
||||
})
|
||||
const mobileCodeTimer = ref(0)
|
||||
const redirect = ref<string>('')
|
||||
const getSmsCode = async () => {
|
||||
await getTenantId()
|
||||
smsVO.smsCode.mobile = loginData.loginForm.mobileNumber
|
||||
await sendSmsCode(smsVO.smsCode).then(async () => {
|
||||
message.success(t('login.SmsSendMsg'))
|
||||
// 设置倒计时
|
||||
mobileCodeTimer.value = 60
|
||||
let msgTimer = setInterval(() => {
|
||||
mobileCodeTimer.value = mobileCodeTimer.value - 1
|
||||
if (mobileCodeTimer.value <= 0) {
|
||||
clearInterval(msgTimer)
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
redirect.value = route?.query?.redirect as string
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
// 获取租户 ID
|
||||
const getTenantId = async () => {
|
||||
if (loginData.tenantEnable === 'true') {
|
||||
const res = await getTenantIdByName(loginData.loginForm.tenantName)
|
||||
setTenantId(res)
|
||||
}
|
||||
}
|
||||
// 登录
|
||||
const signIn = async () => {
|
||||
await getTenantId()
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在加载系统中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
loginLoading.value = true
|
||||
smsVO.loginSms.mobile = loginData.loginForm.mobileNumber
|
||||
smsVO.loginSms.code = loginData.loginForm.code
|
||||
await smsLogin(smsVO.loginSms)
|
||||
.then(async (res) => {
|
||||
setToken(res)
|
||||
if (!redirect.value) {
|
||||
redirect.value = '/'
|
||||
}
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loginLoading.value = false
|
||||
setTimeout(() => {
|
||||
const loadingInstance = ElLoading.service()
|
||||
loadingInstance.close()
|
||||
}, 400)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.anticon) {
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.smsbtn {
|
||||
margin-top: 33px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<el-row v-show="getShow" style="margin-right: -10px; margin-left: -10px">
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-card class="mb-10px text-center" shadow="hover">
|
||||
<Qrcode :logo="logoImg" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-divider class="enter-x">{{ t('login.qrcode') }}</el-divider>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<div class="mt-15px w-[100%]">
|
||||
<XButton :title="t('login.backLogin')" class="w-[100%]" @click="handleBackLogin()" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import logoImg from '@/assets/imgs/logo.png'
|
||||
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||
|
||||
defineOptions({ name: 'QrCodeForm' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const { handleBackLogin, getLoginState } = useLoginState()
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.QR_CODE)
|
||||
</script>
|
||||
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<Form
|
||||
v-show="getShow"
|
||||
:rules="rules"
|
||||
:schema="schema"
|
||||
class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
|
||||
hide-required-asterisk
|
||||
label-position="top"
|
||||
size="large"
|
||||
@register="register"
|
||||
>
|
||||
<template #title>
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
</template>
|
||||
|
||||
<template #code="form">
|
||||
<div class="w-[100%] flex">
|
||||
<el-input v-model="form['code']" :placeholder="t('login.codePlaceholder')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #register>
|
||||
<div class="w-[100%]">
|
||||
<XButton
|
||||
:loading="loading"
|
||||
:title="t('login.register')"
|
||||
class="w-[100%]"
|
||||
type="primary"
|
||||
@click="loginRegister()"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-15px w-[100%]">
|
||||
<XButton :title="t('login.hasUser')" class="w-[100%]" @click="handleBackLogin()" />
|
||||
</div>
|
||||
</template>
|
||||
</Form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormRules } from 'element-plus'
|
||||
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||
import { FormSchema } from '@/types/form'
|
||||
|
||||
defineOptions({ name: 'RegisterForm' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const { required } = useValidator()
|
||||
const { register, elFormRef } = useForm()
|
||||
const { handleBackLogin, getLoginState } = useLoginState()
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
|
||||
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'title',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'username',
|
||||
label: t('login.username'),
|
||||
value: '',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: t('login.usernamePlaceholder')
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'password',
|
||||
label: t('login.password'),
|
||||
value: '',
|
||||
component: 'InputPassword',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
strength: true,
|
||||
placeholder: t('login.passwordPlaceholder')
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'check_password',
|
||||
label: t('login.checkPassword'),
|
||||
value: '',
|
||||
component: 'InputPassword',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
strength: true,
|
||||
placeholder: t('login.passwordPlaceholder')
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
label: t('login.code'),
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'register',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const rules: FormRules = {
|
||||
username: [required()],
|
||||
password: [required()],
|
||||
check_password: [required()],
|
||||
code: [required()]
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const loginRegister = async () => {
|
||||
const formRef = unref(elFormRef)
|
||||
formRef?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
loading.value = true
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div v-show="ssoVisible" class="form-cont">
|
||||
<!-- 应用名 -->
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
<el-tabs class="form" style="float: none" value="uname">
|
||||
<el-tab-pane :label="client.name" name="uname" />
|
||||
</el-tabs>
|
||||
<div>
|
||||
<el-form :model="formData" class="login-form">
|
||||
<!-- 授权范围的选择 -->
|
||||
此第三方应用请求获得以下权限:
|
||||
<el-form-item prop="scopes">
|
||||
<el-checkbox-group v-model="formData.scopes">
|
||||
<el-checkbox
|
||||
v-for="scope in queryParams.scopes"
|
||||
:key="scope"
|
||||
:label="scope"
|
||||
style="display: block; margin-bottom: -10px"
|
||||
>
|
||||
{{ formatScope(scope) }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<!-- 下方的登录按钮 -->
|
||||
<el-form-item class="w-1/1">
|
||||
<el-button
|
||||
:loading="formLoading"
|
||||
class="w-6/10"
|
||||
type="primary"
|
||||
@click.prevent="handleAuthorize(true)"
|
||||
>
|
||||
<span v-if="!formLoading">同意授权</span>
|
||||
<span v-else>授 权 中...</span>
|
||||
</el-button>
|
||||
<el-button class="w-3/10" @click.prevent="handleAuthorize(false)">拒绝</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import * as OAuth2Api from '@/api/login/oauth2'
|
||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
defineOptions({ name: 'SSOLogin' })
|
||||
|
||||
const route = useRoute() // 路由
|
||||
const { currentRoute } = useRouter() // 路由
|
||||
const { getLoginState, setLoginState } = useLoginState()
|
||||
|
||||
const client = ref({
|
||||
// 客户端信息
|
||||
name: '',
|
||||
logo: ''
|
||||
})
|
||||
interface queryType {
|
||||
responseType: string
|
||||
clientId: string
|
||||
redirectUri: string
|
||||
state: string
|
||||
scopes: string[]
|
||||
}
|
||||
const queryParams = reactive<queryType>({
|
||||
// URL 上的 client_id、scope 等参数
|
||||
responseType: '',
|
||||
clientId: '',
|
||||
redirectUri: '',
|
||||
state: '',
|
||||
scopes: [] // 优先从 query 参数获取;如果未传递,从后端获取
|
||||
})
|
||||
const ssoVisible = computed(() => unref(getLoginState) === LoginStateEnum.SSO) // 是否展示 SSO 登录的表单
|
||||
interface formType {
|
||||
scopes: string[]
|
||||
}
|
||||
const formData = reactive<formType>({
|
||||
scopes: [] // 已选中的 scope 数组
|
||||
})
|
||||
const formLoading = ref(false) // 表单是否提交中
|
||||
|
||||
/** 初始化授权信息 */
|
||||
const init = async () => {
|
||||
// 防止在没有登录的情况下循环弹窗
|
||||
if (typeof route.query.client_id === 'undefined') return
|
||||
// 解析参数
|
||||
// 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.yixiang.co&response_type=code&scope=user.read%20user.write
|
||||
// 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.yixiang.co&response_type=code&scope=user.read
|
||||
queryParams.responseType = route.query.response_type as string
|
||||
queryParams.clientId = route.query.client_id as string
|
||||
queryParams.redirectUri = route.query.redirect_uri as string
|
||||
queryParams.state = route.query.state as string
|
||||
if (route.query.scope) {
|
||||
queryParams.scopes = (route.query.scope as string).split(' ')
|
||||
}
|
||||
|
||||
// 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
|
||||
if (queryParams.scopes.length > 0) {
|
||||
const data = await doAuthorize(true, queryParams.scopes, [])
|
||||
if (data) {
|
||||
location.href = data
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取授权页的基本信息
|
||||
const data = await OAuth2Api.getAuthorize(queryParams.clientId)
|
||||
client.value = data.client
|
||||
// 解析 scope
|
||||
let scopes
|
||||
// 1.1 如果 params.scope 非空,则过滤下返回的 scopes
|
||||
if (queryParams.scopes.length > 0) {
|
||||
scopes = []
|
||||
for (const scope of data.scopes) {
|
||||
if (queryParams.scopes.indexOf(scope.key) >= 0) {
|
||||
scopes.push(scope)
|
||||
}
|
||||
}
|
||||
// 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它
|
||||
} else {
|
||||
scopes = data.scopes
|
||||
for (const scope of scopes) {
|
||||
queryParams.scopes.push(scope.key)
|
||||
}
|
||||
}
|
||||
// 生成已选中的 checkedScopes
|
||||
for (const scope of scopes) {
|
||||
if (scope.value) {
|
||||
formData.scopes.push(scope.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理授权的提交 */
|
||||
const handleAuthorize = async (approved) => {
|
||||
// 计算 checkedScopes + uncheckedScopes
|
||||
let checkedScopes
|
||||
let uncheckedScopes
|
||||
if (approved) {
|
||||
// 同意授权,按照用户的选择
|
||||
checkedScopes = formData.scopes
|
||||
uncheckedScopes = queryParams.scopes.filter((item) => checkedScopes.indexOf(item) === -1)
|
||||
} else {
|
||||
// 拒绝,则都是取消
|
||||
checkedScopes = []
|
||||
uncheckedScopes = queryParams.scopes
|
||||
}
|
||||
// 提交授权的请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await doAuthorize(false, checkedScopes, uncheckedScopes)
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
location.href = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 调用授权 API 接口 */
|
||||
const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {
|
||||
return OAuth2Api.authorize(
|
||||
queryParams.responseType,
|
||||
queryParams.clientId,
|
||||
queryParams.redirectUri,
|
||||
queryParams.state,
|
||||
autoApprove,
|
||||
checkedScopes,
|
||||
uncheckedScopes
|
||||
)
|
||||
}
|
||||
|
||||
/** 格式化 scope 文本 */
|
||||
const formatScope = (scope) => {
|
||||
// 格式化 scope 授权范围,方便用户理解。
|
||||
// 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。
|
||||
switch (scope) {
|
||||
case 'user.read':
|
||||
return '访问你的个人信息'
|
||||
case 'user.write':
|
||||
return '修改你的个人信息'
|
||||
default:
|
||||
return scope
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听当前路由为 SSOLogin 时,进行数据的初始化 */
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
if (route.name === 'SSOLogin') {
|
||||
setLoginState(LoginStateEnum.SSO)
|
||||
init()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,8 @@
|
||||
import LoginForm from './LoginForm.vue'
|
||||
import MobileForm from './MobileForm.vue'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import RegisterForm from './RegisterForm.vue'
|
||||
import QrCodeForm from './QrCodeForm.vue'
|
||||
import SSOLoginVue from './SSOLogin.vue'
|
||||
|
||||
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue }
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Ref } from 'vue'
|
||||
|
||||
export enum LoginStateEnum {
|
||||
LOGIN,
|
||||
REGISTER,
|
||||
RESET_PASSWORD,
|
||||
MOBILE,
|
||||
QR_CODE,
|
||||
SSO
|
||||
}
|
||||
|
||||
const currentState = ref(LoginStateEnum.LOGIN)
|
||||
|
||||
export function useLoginState() {
|
||||
function setLoginState(state: LoginStateEnum) {
|
||||
currentState.value = state
|
||||
}
|
||||
const getLoginState = computed(() => currentState.value)
|
||||
|
||||
function handleBackLogin() {
|
||||
setLoginState(LoginStateEnum.LOGIN)
|
||||
}
|
||||
|
||||
return {
|
||||
setLoginState,
|
||||
getLoginState,
|
||||
handleBackLogin
|
||||
}
|
||||
}
|
||||
|
||||
export function useFormValid<T extends Object = any>(formRef: Ref<any>) {
|
||||
async function validForm() {
|
||||
const form = unref(formRef)
|
||||
if (!form) return
|
||||
const data = await form.validate()
|
||||
return data as T
|
||||
}
|
||||
|
||||
return {
|
||||
validForm
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<el-card class="user w-1/3" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ t('profile.user.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<ProfileUser />
|
||||
</el-card>
|
||||
<el-card class="user ml-3 w-2/3" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ t('profile.info.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-tabs v-model="activeName" class="profile-tabs" style="height: 400px" tab-position="top">
|
||||
<el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo">
|
||||
<BasicInfo />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('profile.info.resetPwd')" name="resetPwd">
|
||||
<ResetPwd />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('profile.info.userSocial')" name="userSocial">
|
||||
<UserSocial v-model:activeName="activeName" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'
|
||||
|
||||
const { t } = useI18n()
|
||||
defineOptions({ name: 'Profile' })
|
||||
const activeName = ref('basicInfo')
|
||||
</script>
|
||||
<style scoped>
|
||||
.user {
|
||||
max-height: 960px;
|
||||
padding: 15px 20px 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.el-card .el-card__header, .el-card .el-card__body) {
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
.profile-tabs > .el-tabs__content {
|
||||
padding: 32px;
|
||||
font-weight: 600;
|
||||
color: #6b778c;
|
||||
}
|
||||
|
||||
.el-tabs--left .el-tabs__content {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<Form ref="formRef" :labelWidth="200" :rules="rules" :schema="schema">
|
||||
<template #sex="form">
|
||||
<el-radio-group v-model="form['sex']">
|
||||
<el-radio :label="1">{{ t('profile.user.man') }}</el-radio>
|
||||
<el-radio :label="2">{{ t('profile.user.woman') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</Form>
|
||||
<div style="text-align: center">
|
||||
<XButton :title="t('common.save')" type="primary" @click="submit()" />
|
||||
<XButton :title="t('common.reset')" type="danger" @click="init()" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormRules } from 'element-plus'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import {
|
||||
getUserProfile,
|
||||
updateUserProfile,
|
||||
UserProfileUpdateReqVO
|
||||
} from '@/api/system/user/profile'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'BasicInfo' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const userStore = useUserStore()
|
||||
// 表单校验
|
||||
const rules = reactive<FormRules>({
|
||||
nickname: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }],
|
||||
email: [
|
||||
{ required: true, message: t('profile.rules.mail'), trigger: 'blur' },
|
||||
{
|
||||
type: 'email',
|
||||
message: t('profile.rules.truemail'),
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: t('profile.rules.phone'), trigger: 'blur' },
|
||||
{
|
||||
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||
message: t('profile.rules.truephone'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'nickname',
|
||||
label: t('profile.user.nickname'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
label: t('profile.user.mobile'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
label: t('profile.user.email'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'sex',
|
||||
label: t('profile.user.sex'),
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
}
|
||||
])
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const submit = () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
const data = unref(formRef)?.formModel as UserProfileUpdateReqVO
|
||||
await updateUserProfile(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
const profile = await init()
|
||||
userStore.setUserNicknameAction(profile.nickname)
|
||||
}
|
||||
})
|
||||
}
|
||||
const init = async () => {
|
||||
const res = await getUserProfile()
|
||||
unref(formRef)?.setValues(res)
|
||||
return res
|
||||
}
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<UserAvatar :img="userInfo?.avatar" />
|
||||
</div>
|
||||
<ul class="list-group list-group-striped">
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:user" />
|
||||
{{ t('profile.user.username') }}
|
||||
<div class="pull-right">{{ userInfo?.username }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:phone" />
|
||||
{{ t('profile.user.mobile') }}
|
||||
<div class="pull-right">{{ userInfo?.mobile }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="fontisto:email" />
|
||||
{{ t('profile.user.email') }}
|
||||
<div class="pull-right">{{ userInfo?.email }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="carbon:tree-view-alt" />
|
||||
{{ t('profile.user.dept') }}
|
||||
<div v-if="userInfo?.dept" class="pull-right">{{ userInfo?.dept.name }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:suitcase" />
|
||||
{{ t('profile.user.posts') }}
|
||||
<div v-if="userInfo?.posts" class="pull-right">
|
||||
{{ userInfo?.posts.map((post) => post.name).join(',') }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="icon-park-outline:peoples" />
|
||||
{{ t('profile.user.roles') }}
|
||||
<div v-if="userInfo?.roles" class="pull-right">
|
||||
{{ userInfo?.roles.map((role) => role.name).join(',') }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:calendar" />
|
||||
{{ t('profile.user.createTime') }}
|
||||
<div class="pull-right">{{ formatDate(userInfo.createTime) }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import UserAvatar from './UserAvatar.vue'
|
||||
|
||||
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
||||
|
||||
defineOptions({ name: 'ProfileUser' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const userInfo = ref({} as ProfileVO)
|
||||
const getUserInfo = async () => {
|
||||
const users = await getUserProfile()
|
||||
userInfo.value = users
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getUserInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-center {
|
||||
position: relative;
|
||||
height: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list-group-striped > .list-group-item {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
padding: 11px 0;
|
||||
margin-bottom: -1px;
|
||||
font-size: 13px;
|
||||
border-top: 1px solid #e7eaec;
|
||||
border-bottom: 1px solid #e7eaec;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="password" :rules="rules" :label-width="200">
|
||||
<el-form-item :label="t('profile.password.oldPassword')" prop="oldPassword">
|
||||
<InputPassword v-model="password.oldPassword" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('profile.password.newPassword')" prop="newPassword">
|
||||
<InputPassword v-model="password.newPassword" strength />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('profile.password.confirmPassword')" prop="confirmPassword">
|
||||
<InputPassword v-model="password.confirmPassword" strength />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<XButton :title="t('common.save')" type="primary" @click="submit(formRef)" />
|
||||
<XButton :title="t('common.reset')" type="danger" @click="reset(formRef)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
import { InputPassword } from '@/components/InputPassword'
|
||||
import { updateUserPassword } from '@/api/system/user/profile'
|
||||
|
||||
defineOptions({ name: 'ResetPwd' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const formRef = ref<FormInstance>()
|
||||
const password = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
// 表单校验
|
||||
const equalToPassword = (_rule, value, callback) => {
|
||||
if (password.newPassword !== value) {
|
||||
callback(new Error(t('profile.password.diffPwd')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
oldPassword: [
|
||||
{ required: true, message: t('profile.password.oldPwdMsg'), trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: t('profile.password.newPwdMsg'), trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: t('profile.password.cfPwdMsg'), trigger: 'blur' },
|
||||
{ required: true, validator: equalToPassword, trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const submit = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
await updateUserPassword(password.oldPassword, password.newPassword)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const reset = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="change-avatar">
|
||||
<CropperAvatar
|
||||
ref="cropperRef"
|
||||
:btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }"
|
||||
:showBtn="false"
|
||||
:value="img"
|
||||
width="120px"
|
||||
@change="handelUpload"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { uploadAvatar } from '@/api/system/user/profile'
|
||||
import { CropperAvatar } from '@/components/Cropper'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
|
||||
defineOptions({ name: 'UserAvatar' })
|
||||
|
||||
defineProps({
|
||||
img: propTypes.string.def('')
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
|
||||
const cropperRef = ref()
|
||||
const handelUpload = async ({ data }) => {
|
||||
const res = await uploadAvatar({ avatarFile: data })
|
||||
cropperRef.value.close()
|
||||
userStore.setUserAvatarAction(res.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.change-avatar {
|
||||
img {
|
||||
display: block;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<el-table :data="socialUsers" :show-header="false">
|
||||
<el-table-column fixed="left" title="序号" type="seq" width="60" />
|
||||
<el-table-column align="left" label="社交平台" width="120">
|
||||
<template #default="{ row }">
|
||||
<img :src="row.img" alt="" class="h-5 align-middle" />
|
||||
<p class="mr-5">{{ row.title }}</p>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.openid">
|
||||
已绑定
|
||||
<XTextButton class="mr-5" title="(解绑)" type="primary" @click="unbind(row)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
未绑定
|
||||
<XTextButton class="mr-5" title="(绑定)" type="primary" @click="bind(row)" />
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { SystemUserSocialTypeEnum } from '@/utils/constants'
|
||||
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
||||
import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'
|
||||
|
||||
defineOptions({ name: 'UserSocial' })
|
||||
defineProps<{
|
||||
activeName: string
|
||||
}>()
|
||||
const message = useMessage()
|
||||
const socialUsers = ref<any[]>([])
|
||||
const userInfo = ref<ProfileVO>()
|
||||
|
||||
const initSocial = async () => {
|
||||
socialUsers.value = [] // 重置避免无限增长
|
||||
const res = await getUserProfile()
|
||||
userInfo.value = res
|
||||
for (const i in SystemUserSocialTypeEnum) {
|
||||
const socialUser = { ...SystemUserSocialTypeEnum[i] }
|
||||
socialUsers.value.push(socialUser)
|
||||
if (userInfo.value?.socialUsers) {
|
||||
for (const j in userInfo.value.socialUsers) {
|
||||
if (socialUser.type === userInfo.value.socialUsers[j].type) {
|
||||
socialUser.openid = userInfo.value.socialUsers[j].openid
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const route = useRoute()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:activeName', v: string): void
|
||||
}>()
|
||||
const bindSocial = () => {
|
||||
// 社交绑定
|
||||
const type = getUrlValue('type')
|
||||
const code = route.query.code
|
||||
const state = route.query.state
|
||||
if (!code) {
|
||||
return
|
||||
}
|
||||
socialBind(type, code, state).then(() => {
|
||||
message.success('绑定成功')
|
||||
emit('update:activeName', 'userSocial')
|
||||
})
|
||||
}
|
||||
|
||||
// 双层 encode 需要在回调后进行 decode
|
||||
function getUrlValue(key: string): string {
|
||||
const url = new URL(decodeURIComponent(location.href))
|
||||
return url.searchParams.get(key) ?? ''
|
||||
}
|
||||
|
||||
const bind = (row) => {
|
||||
// 双层 encode 解决钉钉回调 type 参数丢失的问题
|
||||
const redirectUri = location.origin + '/user/profile?' + encodeURIComponent(`type=${row.type}`)
|
||||
// 进行跳转
|
||||
socialAuthRedirect(row.type, encodeURIComponent(redirectUri)).then((res) => {
|
||||
window.location.href = res
|
||||
})
|
||||
}
|
||||
const unbind = async (row) => {
|
||||
const res = await socialUnbind(row.type, row.openid)
|
||||
if (res) {
|
||||
row.openid = undefined
|
||||
}
|
||||
message.success('解绑成功')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await initSocial()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
() => {
|
||||
bindSocial()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
import BasicInfo from './BasicInfo.vue'
|
||||
import ProfileUser from './ProfileUser.vue'
|
||||
import ResetPwd from './ResetPwd.vue'
|
||||
import UserAvatarVue from './UserAvatar.vue'
|
||||
import UserSocial from './UserSocial.vue'
|
||||
|
||||
export { BasicInfo, ProfileUser, ResetPwd, UserAvatarVue, UserSocial }
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'Redirect' })
|
||||
|
||||
const { currentRoute, replace } = useRouter()
|
||||
const { params, query } = unref(currentRoute)
|
||||
const { path, _redirect_type = 'path' } = params
|
||||
|
||||
Reflect.deleteProperty(params, '_redirect_type')
|
||||
Reflect.deleteProperty(params, 'path')
|
||||
|
||||
const _path = Array.isArray(path) ? path.join('/') : path
|
||||
|
||||
if (_redirect_type === 'name') {
|
||||
replace({
|
||||
name: _path,
|
||||
query,
|
||||
params
|
||||
})
|
||||
} else {
|
||||
replace({
|
||||
path: _path.startsWith('/') ? _path : '/' + _path,
|
||||
query
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="快递公司编号" prop="code">
|
||||
<el-input v-model="formData.code" placeholder="请输入快递公司编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="快递公司全称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入快递公司全称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input v-model="formData.sort" placeholder="请输入排序" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as ExpressApi from '@/api/express'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
sort: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
code: [{ required: true, message: '快递公司简称不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '快递公司全称不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ExpressApi.getExpress(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as ExpressApi.ExpressVO
|
||||
if (formType.value === 'create') {
|
||||
await ExpressApi.createExpress(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ExpressApi.updateExpress(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
sort: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="appId" prop="eBusinessID">
|
||||
<el-input v-model="formData.ebusinessID" placeholder="请输入快递鸟appId" />
|
||||
</el-form-item>
|
||||
<el-form-item label="appKey" prop="apiKey">
|
||||
<el-input v-model="formData.apiKey" type="password" placeholder="请输入快递鸟appKey" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否收费套餐" prop="isFree">
|
||||
<el-radio-group v-model="formData.isFree">
|
||||
<el-radio :label="false">是</el-radio>
|
||||
<el-radio :label="true">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label=" " prop="">
|
||||
<el-button @click="submitForm" type="primary">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label=" " prop="">
|
||||
<span><span style="color: red;">yshop-pro系统使用的第三方快递鸟api实现了快递查询,注册地址请点击,</span>
|
||||
<a href="http://www.kdniao.com/reg?from=cbb-yx " style="color:blue">我注册!</a></span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as ExpressApi from '@/api/express'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(true) // 弹窗的是否展示
|
||||
// const dialogTitle = ref('快递鸟配置') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
// const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
ebusinessID: "",
|
||||
apiKey: "",
|
||||
isFree: true
|
||||
|
||||
})
|
||||
const formRules = reactive({
|
||||
appId: [{ required: true, message: '快递鸟appId不能为空', trigger: 'blur' }],
|
||||
appKey: [{ required: true, message: '快递鸟appKey不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
|
||||
/** 提交表单 */
|
||||
//const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value
|
||||
await ExpressApi.postExpressSet(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
// 发送操作成功的事件
|
||||
// emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
const getList = async () => {
|
||||
const data = await ExpressApi.getExpressSet()
|
||||
if (data) {
|
||||
formData.value = data
|
||||
}
|
||||
|
||||
}
|
||||
// /** 重置表单 */
|
||||
// const resetForm = () => {
|
||||
// formData.value = {
|
||||
// appId: undefined,
|
||||
// appKey: undefined
|
||||
// }
|
||||
// formRef.value?.resetFields()
|
||||
// }
|
||||
</script>
|
||||
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公司编码" prop="code">
|
||||
<el-input
|
||||
v-model="queryParams.code"
|
||||
placeholder="请输入快递公司编码"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="公司全称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入快递公司全称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['order:express:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['order:express:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="id" align="center" prop="id" />
|
||||
<el-table-column label="快递公司编码" align="center" prop="code" />
|
||||
<el-table-column label="快递公司全称" align="center" prop="name" />
|
||||
<el-table-column label="排序" align="center" prop="sort" />
|
||||
<el-table-column
|
||||
label="添加时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['order:express:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['order:express:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ExpressForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Express">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as ExpressApi from '@/api/express'
|
||||
import ExpressForm from './ExpressForm.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
code: null,
|
||||
name: null
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ExpressApi.getExpressPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ExpressApi.deleteExpress(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ExpressApi.exportExpress(queryParams)
|
||||
download.excel(data, '快递公司.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :max-height="500" :scroll="true" title="详情" width="800">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="日志主键" min-width="120">
|
||||
{{ detailData.id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="链路追踪">
|
||||
{{ detailData.traceId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="应用名">
|
||||
{{ detailData.applicationName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户信息">
|
||||
{{ detailData.userId }}
|
||||
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户 IP">
|
||||
{{ detailData.userIp }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户 UA">
|
||||
{{ detailData.userAgent }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求信息">
|
||||
{{ detailData.requestMethod }} {{ detailData.requestUrl }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求参数">
|
||||
{{ detailData.requestParams }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求结果">
|
||||
{{ detailData.responseBody }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求时间">
|
||||
{{ formatDate(detailData.beginTime) }} ~ {{ formatDate(detailData.endTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求耗时">{{ detailData.duration }} ms</el-descriptions-item>
|
||||
<el-descriptions-item label="操作结果">
|
||||
<div v-if="detailData.resultCode === 0">正常</div>
|
||||
<div v-else-if="detailData.resultCode > 0">
|
||||
失败 | {{ detailData.resultCode }} | {{ detailData.resultMsg }}
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作模块">
|
||||
{{ detailData.operateModule }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作名">
|
||||
{{ detailData.operateName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作名">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_OPERATE_TYPE" :value="detailData.operateType" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as ApiAccessLog from '@/api/infra/apiAccessLog'
|
||||
|
||||
defineOptions({ name: 'ApiAccessLogDetail' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单地加载中
|
||||
const detailData = ref({} as ApiAccessLog.ApiAccessLogVO) // 详情数据
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (data: ApiAccessLog.ApiAccessLogVO) => {
|
||||
dialogVisible.value = true
|
||||
// 设置数据
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detailData.value = data
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="用户编号" prop="userId">
|
||||
<el-input
|
||||
v-model="queryParams.userId"
|
||||
placeholder="请输入用户编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户类型" prop="userType">
|
||||
<el-select
|
||||
v-model="queryParams.userType"
|
||||
placeholder="请选择用户类型"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="应用名" prop="applicationName">
|
||||
<el-input
|
||||
v-model="queryParams.applicationName"
|
||||
placeholder="请输入应用名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求时间" prop="beginTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.beginTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行时长" prop="duration">
|
||||
<el-input
|
||||
v-model="queryParams.duration"
|
||||
placeholder="请输入执行时长"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结果码" prop="resultCode">
|
||||
<el-input
|
||||
v-model="queryParams.resultCode"
|
||||
placeholder="请输入结果码"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:api-access-log:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="日志编号" align="center" prop="id" width="100" fix="right" />
|
||||
<el-table-column label="用户编号" align="center" prop="userId" />
|
||||
<el-table-column label="用户类型" align="center" prop="userType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应用名" align="center" prop="applicationName" width="150" />
|
||||
<el-table-column label="请求方法" align="center" prop="requestMethod" width="80" />
|
||||
<el-table-column label="请求地址" align="center" prop="requestUrl" width="500" />
|
||||
<el-table-column label="请求时间" align="center" prop="beginTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.beginTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行时长" align="center" prop="duration" width="180">
|
||||
<template #default="scope"> {{ scope.row.duration }} ms </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作结果" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
{{ scope.row.resultCode === 0 ? '成功' : '失败(' + scope.row.resultMsg + ')' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作模块" align="center" prop="operateModule" width="180" />
|
||||
<el-table-column label="操作名" align="center" prop="operateName" width="180" />
|
||||
<el-table-column label="操作类型" align="center" prop="operateType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_OPERATE_TYPE" :value="scope.row.operateType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="60">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openDetail(scope.row)"
|
||||
v-hasPermi="['infra:api-access-log:query']"
|
||||
>
|
||||
详细
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:详情 -->
|
||||
<ApiAccessLogDetail ref="detailRef" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as ApiAccessLogApi from '@/api/infra/apiAccessLog'
|
||||
import ApiAccessLogDetail from './ApiAccessLogDetail.vue'
|
||||
|
||||
defineOptions({ name: 'InfraApiAccessLog' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
userId: null,
|
||||
userType: null,
|
||||
applicationName: null,
|
||||
requestUrl: null,
|
||||
duration: null,
|
||||
resultCode: null,
|
||||
beginTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ApiAccessLogApi.getApiAccessLogPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 详情操作 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (data: ApiAccessLogApi.ApiAccessLogVO) => {
|
||||
detailRef.value.open(data)
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ApiAccessLogApi.exportApiAccessLog(queryParams)
|
||||
download.excel(data, 'API 访问日志.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :max-height="500" :scroll="true" title="详情" width="800">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="日志主键" min-width="120">
|
||||
{{ detailData.id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="链路追踪">
|
||||
{{ detailData.traceId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="应用名">
|
||||
{{ detailData.applicationName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户编号">
|
||||
{{ detailData.userId }}
|
||||
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户 IP">
|
||||
{{ detailData.userIp }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户 UA">
|
||||
{{ detailData.userAgent }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求信息">
|
||||
{{ detailData.requestMethod }} {{ detailData.requestUrl }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求参数">
|
||||
{{ detailData.requestParams }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="异常时间">
|
||||
{{ formatDate(detailData.exceptionTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="异常名">
|
||||
{{ detailData.exceptionName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.exceptionStackTrace" label="异常堆栈">
|
||||
<el-input
|
||||
v-model="detailData.exceptionStackTrace"
|
||||
:autosize="{ maxRows: 20 }"
|
||||
:readonly="true"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="处理状态">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS"
|
||||
:value="detailData.processStatus"
|
||||
/>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.processUserId" label="处理人">
|
||||
{{ detailData.processUserId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.processTime" label="处理时间">
|
||||
{{ formatDate(detailData.processTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as ApiErrorLog from '@/api/infra/apiErrorLog'
|
||||
|
||||
defineOptions({ name: 'ApiErrorLogDetail' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref({} as ApiErrorLog.ApiErrorLogVO) // 详情数据
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (data: ApiErrorLog.ApiErrorLogVO) => {
|
||||
dialogVisible.value = true
|
||||
// 设置数据
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detailData.value = data
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="用户编号" prop="userId">
|
||||
<el-input
|
||||
v-model="queryParams.userId"
|
||||
placeholder="请输入用户编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户类型" prop="userType">
|
||||
<el-select
|
||||
v-model="queryParams.userType"
|
||||
placeholder="请选择用户类型"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="应用名" prop="applicationName">
|
||||
<el-input
|
||||
v-model="queryParams.applicationName"
|
||||
placeholder="请输入应用名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="异常时间" prop="exceptionTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.exceptionTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="处理状态" prop="processStatus">
|
||||
<el-select
|
||||
v-model="queryParams.processStatus"
|
||||
placeholder="请选择处理状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:api-error-log:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="日志编号" align="center" prop="id" />
|
||||
<el-table-column label="用户编号" align="center" prop="userId" />
|
||||
<el-table-column label="用户类型" align="center" prop="userType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应用名" align="center" prop="applicationName" width="200" />
|
||||
<el-table-column label="请求方法" align="center" prop="requestMethod" width="80" />
|
||||
<el-table-column label="请求地址" align="center" prop="requestUrl" width="180" />
|
||||
<el-table-column
|
||||
label="异常发生时间"
|
||||
align="center"
|
||||
prop="exceptionTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="异常名" align="center" prop="exceptionName" width="180" />
|
||||
<el-table-column label="处理状态" align="center" prop="processStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS"
|
||||
:value="scope.row.processStatus"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openDetail(scope.row)"
|
||||
v-hasPermi="['infra:api-error-log:query']"
|
||||
>
|
||||
详细
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
|
||||
@click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.DONE)"
|
||||
v-hasPermi="['infra:api-error-log:update-status']"
|
||||
>
|
||||
已处理
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
|
||||
@click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.IGNORE)"
|
||||
v-hasPermi="['infra:api-error-log:update-status']"
|
||||
>
|
||||
已忽略
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:详情 -->
|
||||
<ApiErrorLogDetail ref="detailRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as ApiErrorLogApi from '@/api/infra/apiErrorLog'
|
||||
import ApiErrorLogDetail from './ApiErrorLogDetail.vue'
|
||||
import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'InfraApiErrorLog' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
userId: null,
|
||||
userType: null,
|
||||
applicationName: null,
|
||||
requestUrl: null,
|
||||
processStatus: null,
|
||||
exceptionTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ApiErrorLogApi.getApiErrorLogPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 详情操作 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (data: ApiErrorLogApi.ApiErrorLogVO) => {
|
||||
detailRef.value.open(data)
|
||||
}
|
||||
|
||||
/** 处理已处理 / 已忽略的操作 **/
|
||||
const handleProcess = async (id: number, processStatus: number) => {
|
||||
try {
|
||||
// 操作的二次确认
|
||||
const type = processStatus === InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略'
|
||||
await message.confirm('确认标记为' + type + '?')
|
||||
// 执行操作
|
||||
await ApiErrorLogApi.updateApiErrorLogPage(id, processStatus)
|
||||
await message.success(type)
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ApiErrorLogApi.exportApiErrorLog(queryParams)
|
||||
download.excel(data, '异常日志.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<div class="float-right mb-2">
|
||||
<el-button size="small" type="primary" @click="showJson">生成 JSON</el-button>
|
||||
<el-button size="small" type="success" @click="showOption">生成 Options</el-button>
|
||||
<el-button size="small" type="danger" @click="showTemplate">生成组件</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 表单设计器 -->
|
||||
<FcDesigner ref="designer" height="780px" />
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 弹窗:表单预览 -->
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" max-height="600">
|
||||
<div v-if="dialogVisible" ref="editor">
|
||||
<el-button style="float: right" @click="copy(formData)">
|
||||
{{ t('common.copy') }}
|
||||
</el-button>
|
||||
<el-scrollbar height="580">
|
||||
<div>
|
||||
<pre><code v-dompurify-html="highlightedCode(formData)" class="hljs"></code></pre>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useFormCreateDesigner } from '@/components/FormCreate'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { isString } from '@/utils/is'
|
||||
|
||||
import hljs from 'highlight.js' // 导入代码高亮文件
|
||||
import 'highlight.js/styles/github.css' // 导入代码高亮样式
|
||||
import xml from 'highlight.js/lib/languages/java'
|
||||
import json from 'highlight.js/lib/languages/json'
|
||||
import formCreate from '@form-create/element-ui'
|
||||
|
||||
defineOptions({ name: 'InfraBuild' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息
|
||||
|
||||
const designer = ref() // 表单设计器
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formType = ref(-1) // 表单的类型:0 - 生成 JSON;1 - 生成 Options;2 - 生成组件
|
||||
const formData = ref('') // 表单数据
|
||||
useFormCreateDesigner(designer) // 表单设计器增强
|
||||
|
||||
/** 打开弹窗 */
|
||||
const openModel = (title: string) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = title
|
||||
}
|
||||
|
||||
/** 生成 JSON */
|
||||
const showJson = () => {
|
||||
openModel('生成 JSON')
|
||||
formType.value = 0
|
||||
formData.value = designer.value.getRule()
|
||||
}
|
||||
|
||||
/** 生成 Options */
|
||||
const showOption = () => {
|
||||
openModel('生成 Options')
|
||||
formType.value = 1
|
||||
formData.value = designer.value.getOption()
|
||||
}
|
||||
|
||||
/** 生成组件 */
|
||||
const showTemplate = () => {
|
||||
openModel('生成组件')
|
||||
formType.value = 2
|
||||
formData.value = makeTemplate()
|
||||
}
|
||||
|
||||
const makeTemplate = () => {
|
||||
const rule = designer.value.getRule()
|
||||
const opt = designer.value.getOption()
|
||||
return `<template>
|
||||
<form-create
|
||||
v-model:api="fApi"
|
||||
:rule="rule"
|
||||
:option="option"
|
||||
@submit="onSubmit"
|
||||
></form-create>
|
||||
</template>
|
||||
<script setup lang=ts>
|
||||
const faps = ref(null)
|
||||
const rule = ref('')
|
||||
const option = ref('')
|
||||
const init = () => {
|
||||
rule.value = formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\', '\\\\')}')
|
||||
option.value = formCreate.parseJson('${JSON.stringify(opt)}')
|
||||
}
|
||||
const onSubmit = (formData) => {
|
||||
//todo 提交表单
|
||||
}
|
||||
init()
|
||||
<\/script>`
|
||||
}
|
||||
|
||||
/** 复制 **/
|
||||
const copy = async (text: string) => {
|
||||
const { copy, copied, isSupported } = useClipboard({ source: text })
|
||||
if (!isSupported) {
|
||||
message.error(t('common.copyError'))
|
||||
} else {
|
||||
await copy()
|
||||
if (unref(copied)) {
|
||||
message.success(t('common.copySuccess'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码高亮
|
||||
*/
|
||||
const highlightedCode = (code) => {
|
||||
// 处理语言和代码
|
||||
let language = 'json'
|
||||
if (formType.value === 2) {
|
||||
language = 'xml'
|
||||
}
|
||||
if (!isString(code)) {
|
||||
code = JSON.stringify(code)
|
||||
}
|
||||
// 高亮
|
||||
const result = hljs.highlight(language, code, true)
|
||||
return result.value || ' '
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
// 注册代码高亮的各种语言
|
||||
hljs.registerLanguage('xml', xml)
|
||||
hljs.registerLanguage('json', json)
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<ContentWrap v-loading="formLoading">
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="基本信息" name="basicInfo">
|
||||
<basic-info-form ref="basicInfoRef" :table="formData.table" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="字段信息" name="colum">
|
||||
<colum-info-form ref="columInfoRef" :columns="formData.columns" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="生成信息" name="generateInfo">
|
||||
<generate-info-form
|
||||
ref="generateInfoRef"
|
||||
:table="formData.table"
|
||||
:columns="formData.columns"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-form>
|
||||
<el-form-item style="float: right">
|
||||
<el-button :loading="formLoading" type="primary" @click="submitForm">保存</el-button>
|
||||
<el-button @click="close">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { BasicInfoForm, ColumInfoForm, GenerateInfoForm } from './components'
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
|
||||
defineOptions({ name: 'InfraCodegenEditTable' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const { query } = useRoute() // 查询参数
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const activeName = ref('colum') // Tag 激活的窗口
|
||||
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>()
|
||||
const columInfoRef = ref<ComponentRef<typeof ColumInfoForm>>()
|
||||
const generateInfoRef = ref<ComponentRef<typeof GenerateInfoForm>>()
|
||||
const formData = ref<CodegenApi.CodegenUpdateReqVO>({
|
||||
table: {},
|
||||
columns: []
|
||||
})
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = async () => {
|
||||
const id = query.id as unknown as number
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await CodegenApi.getCodegenTable(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async () => {
|
||||
// 参数校验
|
||||
if (!unref(formData)) return
|
||||
await unref(basicInfoRef)?.validate()
|
||||
await unref(generateInfoRef)?.validate()
|
||||
try {
|
||||
// 提交请求
|
||||
await CodegenApi.updateCodegenTable(formData.value)
|
||||
message.success(t('common.updateSuccess'))
|
||||
close()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 关闭按钮 */
|
||||
const close = () => {
|
||||
delView(unref(currentRoute))
|
||||
push('/infra/codegen')
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="导入表" width="800px">
|
||||
<!-- 搜索栏 -->
|
||||
<el-form ref="queryFormRef" :inline="true" :model="queryParams" label-width="68px">
|
||||
<el-form-item label="数据源" prop="dataSourceConfigId">
|
||||
<el-select
|
||||
v-model="queryParams.dataSourceConfigId"
|
||||
class="!w-240px"
|
||||
placeholder="请选择数据源"
|
||||
>
|
||||
<el-option
|
||||
v-for="config in dataSourceConfigList"
|
||||
:key="config.id"
|
||||
:label="config.name"
|
||||
:value="config.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="表名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入表名称"
|
||||
@keyup.enter="getList"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="表描述" prop="comment">
|
||||
<el-input
|
||||
v-model="queryParams.comment"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入表描述"
|
||||
@keyup.enter="getList"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getList">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 列表 -->
|
||||
<el-row>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="dbTableLoading"
|
||||
:data="dbTableList"
|
||||
height="260px"
|
||||
@row-click="handleRowClick"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column :show-overflow-tooltip="true" label="表名称" prop="name" />
|
||||
<el-table-column :show-overflow-tooltip="true" label="表描述" prop="comment" />
|
||||
</el-table>
|
||||
</el-row>
|
||||
<!-- 操作 -->
|
||||
<template #footer>
|
||||
<el-button :disabled="tableList.length === 0" type="primary" @click="handleImportTable">
|
||||
导入
|
||||
</el-button>
|
||||
<el-button @click="close">关闭</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
|
||||
import { ElTable } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'InfraCodegenImportTable' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dbTableLoading = ref(true) // 数据源的加载中
|
||||
const dbTableList = ref<CodegenApi.DatabaseTableVO[]>([]) // 表的列表
|
||||
const queryParams = reactive({
|
||||
name: undefined,
|
||||
comment: undefined,
|
||||
dataSourceConfigId: 0
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const dataSourceConfigList = ref<DataSourceConfigApi.DataSourceConfigVO[]>([]) // 数据源列表
|
||||
|
||||
/** 查询表数据 */
|
||||
const getList = async () => {
|
||||
dbTableLoading.value = true
|
||||
try {
|
||||
dbTableList.value = await CodegenApi.getSchemaTableList(queryParams)
|
||||
} finally {
|
||||
dbTableLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置操作 */
|
||||
const resetQuery = async () => {
|
||||
queryParams.name = undefined
|
||||
queryParams.comment = undefined
|
||||
queryParams.dataSourceConfigId = dataSourceConfigList.value[0].id as number
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
// 加载数据源的列表
|
||||
dataSourceConfigList.value = await DataSourceConfigApi.getDataSourceConfigList()
|
||||
queryParams.dataSourceConfigId = dataSourceConfigList.value[0].id as number
|
||||
dialogVisible.value = true
|
||||
// 加载表的列表
|
||||
await getList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 关闭弹窗 */
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
tableList.value = []
|
||||
}
|
||||
|
||||
const tableRef = ref<typeof ElTable>() // 表格的 Ref
|
||||
const tableList = ref<string[]>([]) // 选中的表名
|
||||
|
||||
/** 处理某一行的点击 */
|
||||
const handleRowClick = (row) => {
|
||||
unref(tableRef)?.toggleRowSelection(row)
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection) => {
|
||||
tableList.value = selection.map((item) => item.name)
|
||||
}
|
||||
|
||||
/** 导入按钮操作 */
|
||||
const handleImportTable = async () => {
|
||||
await CodegenApi.createCodegenList({
|
||||
dataSourceConfigId: queryParams.dataSourceConfigId,
|
||||
tableNames: tableList.value
|
||||
})
|
||||
message.success('导入成功')
|
||||
emit('success')
|
||||
close()
|
||||
}
|
||||
const emit = defineEmits(['success'])
|
||||
</script>
|
||||
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="dialogVisible"
|
||||
align-center
|
||||
class="app-infra-codegen-preview-container"
|
||||
title="代码预览"
|
||||
width="80%"
|
||||
>
|
||||
<div class="flex">
|
||||
<!-- 代码目录树 -->
|
||||
<el-card
|
||||
v-loading="loading"
|
||||
:gutter="12"
|
||||
class="w-1/3"
|
||||
element-loading-text="生成文件目录中..."
|
||||
shadow="hover"
|
||||
>
|
||||
<el-scrollbar height="calc(100vh - 88px - 40px)">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="preview.fileTree"
|
||||
:expand-on-click-node="false"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="id"
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
<!-- 代码 -->
|
||||
<el-card
|
||||
v-loading="loading"
|
||||
:gutter="12"
|
||||
class="ml-3 w-2/3"
|
||||
element-loading-text="加载代码中..."
|
||||
shadow="hover"
|
||||
>
|
||||
<el-tabs v-model="preview.activeName">
|
||||
<el-tab-pane
|
||||
v-for="item in previewCodegen"
|
||||
:key="item.filePath"
|
||||
:label="item.filePath.substring(item.filePath.lastIndexOf('/') + 1)"
|
||||
:name="item.filePath"
|
||||
>
|
||||
<el-button class="float-right" text type="primary" @click="copy(item.code)">
|
||||
{{ t('common.copy') }}
|
||||
</el-button>
|
||||
<el-scrollbar height="600px">
|
||||
<pre><code v-dompurify-html="highlightedCode(item)" class="hljs"></code></pre>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { handleTree2 } from '@/utils/tree'
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
|
||||
import hljs from 'highlight.js' // 导入代码高亮文件
|
||||
import 'highlight.js/styles/github.css' // 导入代码高亮样式
|
||||
import java from 'highlight.js/lib/languages/java'
|
||||
import xml from 'highlight.js/lib/languages/java'
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
import sql from 'highlight.js/lib/languages/sql'
|
||||
import typescript from 'highlight.js/lib/languages/typescript'
|
||||
|
||||
defineOptions({ name: 'InfraCodegenPreviewCode' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const loading = ref(false) // 加载中的状态
|
||||
const preview = reactive({
|
||||
fileTree: [], // 文件树
|
||||
activeName: '' // 激活的文件名
|
||||
})
|
||||
const previewCodegen = ref<CodegenApi.CodegenPreviewVO[]>()
|
||||
|
||||
/** 点击文件 */
|
||||
const handleNodeClick = async (data, node) => {
|
||||
if (node && !node.isLeaf) {
|
||||
return false
|
||||
}
|
||||
preview.activeName = data.id
|
||||
}
|
||||
|
||||
/** 生成 files 目录 **/
|
||||
interface filesType {
|
||||
id: string
|
||||
label: string
|
||||
parentId: string
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
try {
|
||||
loading.value = true
|
||||
// 生成代码
|
||||
const data = await CodegenApi.previewCodegen(id)
|
||||
previewCodegen.value = data
|
||||
// 处理文件
|
||||
let file = handleFiles(data)
|
||||
preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/')
|
||||
// 点击首个文件
|
||||
preview.activeName = data[0].filePath
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 处理文件 */
|
||||
const handleFiles = (datas: CodegenApi.CodegenPreviewVO[]) => {
|
||||
let exists = {} // key:file 的 id;value:true
|
||||
let files: filesType[] = []
|
||||
// 遍历每个元素
|
||||
for (const data of datas) {
|
||||
let paths = data.filePath.split('/')
|
||||
let fullPath = '' // 从头开始的路径,用于生成 id
|
||||
// 特殊处理 java 文件
|
||||
if (paths[paths.length - 1].indexOf('.java') >= 0) {
|
||||
let newPaths: string[] = []
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
let path = paths[i]
|
||||
if (path !== 'java') {
|
||||
newPaths.push(path)
|
||||
continue
|
||||
}
|
||||
newPaths.push(path)
|
||||
// 特殊处理中间的 package,进行合并
|
||||
let tmp = ''
|
||||
while (i < paths.length) {
|
||||
path = paths[i + 1]
|
||||
if (
|
||||
path === 'controller' ||
|
||||
path === 'convert' ||
|
||||
path === 'dal' ||
|
||||
path === 'enums' ||
|
||||
path === 'service' ||
|
||||
path === 'vo' || // 下面三个,主要是兜底。可能考虑到有人改了包结构
|
||||
path === 'mysql' ||
|
||||
path === 'dataobject'
|
||||
) {
|
||||
break
|
||||
}
|
||||
tmp = tmp ? tmp + '.' + path : path
|
||||
i++
|
||||
}
|
||||
if (tmp) {
|
||||
newPaths.push(tmp)
|
||||
}
|
||||
}
|
||||
paths = newPaths
|
||||
}
|
||||
// 遍历每个 path, 拼接成树
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
// 已经添加到 files 中,则跳过
|
||||
let oldFullPath = fullPath
|
||||
// 下面的 replaceAll 的原因,是因为上面包处理了,导致和 tabs 不匹配,所以 replaceAll 下
|
||||
fullPath = fullPath.length === 0 ? paths[i] : fullPath.replaceAll('.', '/') + '/' + paths[i]
|
||||
if (exists[fullPath]) {
|
||||
continue
|
||||
}
|
||||
// 添加到 files 中
|
||||
exists[fullPath] = true
|
||||
files.push({
|
||||
id: fullPath,
|
||||
label: paths[i],
|
||||
parentId: oldFullPath || '/' // "/" 为根节点
|
||||
})
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
/** 复制 **/
|
||||
const copy = async (text: string) => {
|
||||
const { copy, copied, isSupported } = useClipboard({ source: text })
|
||||
if (!isSupported) {
|
||||
message.error(t('common.copyError'))
|
||||
return
|
||||
}
|
||||
await copy()
|
||||
if (unref(copied)) {
|
||||
message.success(t('common.copySuccess'))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码高亮
|
||||
*/
|
||||
const highlightedCode = (item) => {
|
||||
const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
|
||||
const result = hljs.highlight(language, item.code || '', true)
|
||||
return result.value || ' '
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
// 注册代码高亮的各种语言
|
||||
hljs.registerLanguage('java', java)
|
||||
hljs.registerLanguage('xml', xml)
|
||||
hljs.registerLanguage('html', xml)
|
||||
hljs.registerLanguage('vue', xml)
|
||||
hljs.registerLanguage('javascript', javascript)
|
||||
hljs.registerLanguage('sql', sql)
|
||||
hljs.registerLanguage('typescript', typescript)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.app-infra-codegen-preview-container {
|
||||
.el-scrollbar .el-scrollbar__wrap .el-scrollbar__view {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input v-model="formData.tableName" placeholder="请输入仓库名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input v-model="formData.tableComment" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="className">
|
||||
<template #label>
|
||||
<span>
|
||||
实体类名称
|
||||
<el-tooltip
|
||||
content="默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。"
|
||||
placement="top"
|
||||
>
|
||||
<Icon class="" icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.className" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input v-model="formData.author" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" :rows="3" type="textarea" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
import { PropType } from 'vue'
|
||||
|
||||
defineOptions({ name: 'InfraCodegenBasicInfoForm' })
|
||||
|
||||
const props = defineProps({
|
||||
table: {
|
||||
type: Object as PropType<Nullable<CodegenApi.CodegenTableVO>>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
const formData = ref({
|
||||
tableName: '',
|
||||
tableComment: '',
|
||||
className: '',
|
||||
author: '',
|
||||
remark: ''
|
||||
})
|
||||
const rules = reactive({
|
||||
tableName: [required],
|
||||
tableComment: [required],
|
||||
className: [required],
|
||||
author: [required]
|
||||
})
|
||||
|
||||
/** 监听 table 属性,复制给 formData 属性 */
|
||||
watch(
|
||||
() => props.table,
|
||||
(table) => {
|
||||
if (!table) return
|
||||
formData.value = table
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
validate: async () => unref(formRef)?.validate()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<el-table ref="dragTable" :data="formData" :max-height="tableHeight" row-key="columnId">
|
||||
<el-table-column
|
||||
:show-overflow-tooltip="true"
|
||||
label="字段列名"
|
||||
min-width="10%"
|
||||
prop="columnName"
|
||||
/>
|
||||
<el-table-column label="字段描述" min-width="10%">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.columnComment" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:show-overflow-tooltip="true"
|
||||
label="物理类型"
|
||||
min-width="10%"
|
||||
prop="dataType"
|
||||
/>
|
||||
<el-table-column label="Java类型" min-width="11%">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.javaType">
|
||||
<el-option label="Long" value="Long" />
|
||||
<el-option label="String" value="String" />
|
||||
<el-option label="Integer" value="Integer" />
|
||||
<el-option label="Double" value="Double" />
|
||||
<el-option label="BigDecimal" value="BigDecimal" />
|
||||
<el-option label="LocalDateTime" value="LocalDateTime" />
|
||||
<el-option label="Boolean" value="Boolean" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="java属性" min-width="10%">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.javaField" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="插入" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.createOperation" false-label="false" true-label="true" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编辑" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.updateOperation" false-label="false" true-label="true" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="列表" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox
|
||||
v-model="scope.row.listOperationResult"
|
||||
false-label="false"
|
||||
true-label="true"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.listOperation" false-label="false" true-label="true" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询方式" min-width="10%">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.listOperationCondition">
|
||||
<el-option label="=" value="=" />
|
||||
<el-option label="!=" value="!=" />
|
||||
<el-option label=">" value=">" />
|
||||
<el-option label=">=" value=">=" />
|
||||
<el-option label="<" value="<>" />
|
||||
<el-option label="<=" value="<=" />
|
||||
<el-option label="LIKE" value="LIKE" />
|
||||
<el-option label="BETWEEN" value="BETWEEN" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="允许空" min-width="5%">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.nullable" false-label="false" true-label="true" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="显示类型" min-width="12%">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.htmlType">
|
||||
<el-option label="文本框" value="input" />
|
||||
<el-option label="文本域" value="textarea" />
|
||||
<el-option label="下拉框" value="select" />
|
||||
<el-option label="单选框" value="radio" />
|
||||
<el-option label="复选框" value="checkbox" />
|
||||
<el-option label="日期控件" value="datetime" />
|
||||
<el-option label="图片上传" value="imageUpload" />
|
||||
<el-option label="文件上传" value="fileUpload" />
|
||||
<el-option label="富文本控件" value="editor" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="字典类型" min-width="12%">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="dict in dictOptions"
|
||||
:key="dict.id"
|
||||
:label="dict.name"
|
||||
:value="dict.type"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="示例" min-width="10%">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.example" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue'
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
import * as DictDataApi from '@/api/system/dict/dict.type'
|
||||
|
||||
defineOptions({ name: 'InfraCodegenColumInfoForm' })
|
||||
|
||||
const props = defineProps({
|
||||
columns: {
|
||||
type: Array as unknown as PropType<CodegenApi.CodegenColumnVO[]>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const formData = ref<CodegenApi.CodegenColumnVO[]>([])
|
||||
const tableHeight = document.documentElement.scrollHeight - 350 + 'px'
|
||||
|
||||
/** 查询字典下拉列表 */
|
||||
const dictOptions = ref<DictDataApi.DictTypeVO[]>()
|
||||
const getDictOptions = async () => {
|
||||
dictOptions.value = await DictDataApi.getSimpleDictTypeList()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.columns,
|
||||
(columns) => {
|
||||
if (!columns) return
|
||||
formData.value = columns
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await getDictOptions()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,374 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="150px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="生成模板" prop="templateType">
|
||||
<el-select v-model="formData.templateType">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="前端类型" prop="frontType">
|
||||
<el-select v-model="formData.frontType">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="生成场景" prop="scene">
|
||||
<el-select v-model="formData.scene">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
上级菜单
|
||||
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-tree-select
|
||||
v-model="formData.parentMenuId"
|
||||
:data="menus"
|
||||
:props="menuTreeProps"
|
||||
check-strictly
|
||||
node-key="id"
|
||||
placeholder="请选择系统菜单"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="moduleName">
|
||||
<template #label>
|
||||
<span>
|
||||
模块名
|
||||
<el-tooltip
|
||||
content="模块名,即一级目录,例如 system、infra、tool 等等"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.moduleName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="businessName">
|
||||
<template #label>
|
||||
<span>
|
||||
业务名
|
||||
<el-tooltip
|
||||
content="业务名,即二级目录,例如 user、permission、dict 等等"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.businessName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- <el-col :span="12">-->
|
||||
<!-- <el-form-item prop="businessPackage">-->
|
||||
<!-- <span slot="label">-->
|
||||
<!-- 业务包-->
|
||||
<!-- <el-tooltip content="业务包,自定义二级目录。例如说,我们希望将 dictType 和 dictData 归类成 dict 业务" placement="top">-->
|
||||
<!-- <i class="el-icon-question"></i>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </span>-->
|
||||
<!-- <el-input v-model="formData.businessPackage" />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="className">
|
||||
<template #label>
|
||||
<span>
|
||||
类名称
|
||||
<el-tooltip
|
||||
content="类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.className" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="classComment">
|
||||
<template #label>
|
||||
<span>
|
||||
类描述
|
||||
<el-tooltip content="用作类描述,例如 用户" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.classComment" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="formData.genType === '1'" :span="24">
|
||||
<el-form-item prop="genPath">
|
||||
<template #label>
|
||||
<span>
|
||||
自定义路径
|
||||
<el-tooltip
|
||||
content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.genPath">
|
||||
<template #append>
|
||||
<el-dropdown>
|
||||
<el-button type="primary">
|
||||
最近路径快速选择
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="formData.genPath = '/'">
|
||||
恢复默认的生成基础路径
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 树表信息 -->
|
||||
<el-row v-if="formData.templateType == 2">
|
||||
<el-col :span="24">
|
||||
<h4 class="form-header">树表信息</h4>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="treeParentColumnId">
|
||||
<template #label>
|
||||
<span>
|
||||
父编号字段
|
||||
<el-tooltip content="树显示的父编码字段名, 如:parent_Id" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-select v-model="formData.treeParentColumnId" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in props.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="treeNameColumnId">
|
||||
<template #label>
|
||||
<span>
|
||||
树名称字段
|
||||
<el-tooltip content="树节点的显示名称字段名, 如:dept_name" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-select v-model="formData.treeNameColumnId" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in props.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 主表信息 -->
|
||||
<el-row v-if="formData.templateType == 15">
|
||||
<el-col :span="24">
|
||||
<h4 class="form-header">主表信息</h4>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="masterTableId">
|
||||
<template #label>
|
||||
<span>
|
||||
关联的主表
|
||||
<el-tooltip content="关联主表(父表)的表名, 如:system_user" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-select v-model="formData.masterTableId" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(table0, index) in tables"
|
||||
:key="index"
|
||||
:label="table0.tableName + ':' + table0.tableComment"
|
||||
:value="table0.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="subJoinColumnId">
|
||||
<template #label>
|
||||
<span>
|
||||
子表关联的字段
|
||||
<el-tooltip content="子表关联的字段, 如:user_id" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-select v-model="formData.subJoinColumnId" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in props.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="subJoinMany">
|
||||
<template #label>
|
||||
<span>
|
||||
关联关系
|
||||
<el-tooltip content="主表与子表的关联关系" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-radio-group v-model="formData.subJoinMany" placeholder="请选择">
|
||||
<el-radio :label="true">一对多</el-radio>
|
||||
<el-radio :label="false">一对一</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
import * as MenuApi from '@/api/system/menu'
|
||||
import { PropType } from 'vue'
|
||||
|
||||
defineOptions({ name: 'InfraCodegenGenerateInfoForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const props = defineProps({
|
||||
table: {
|
||||
type: Object as PropType<Nullable<CodegenApi.CodegenTableVO>>,
|
||||
default: () => null
|
||||
},
|
||||
columns: {
|
||||
type: Array as unknown as PropType<CodegenApi.CodegenColumnVO[]>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
const formData = ref({
|
||||
templateType: null,
|
||||
frontType: null,
|
||||
scene: null,
|
||||
moduleName: '',
|
||||
businessName: '',
|
||||
className: '',
|
||||
classComment: '',
|
||||
parentMenuId: null,
|
||||
genPath: '',
|
||||
genType: '',
|
||||
masterTableId: undefined,
|
||||
subJoinColumnId: undefined,
|
||||
subJoinMany: undefined,
|
||||
treeParentColumnId: undefined,
|
||||
treeNameColumnId: undefined
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
templateType: [required],
|
||||
frontType: [required],
|
||||
scene: [required],
|
||||
moduleName: [required],
|
||||
businessName: [required],
|
||||
businessPackage: [required],
|
||||
className: [required],
|
||||
classComment: [required],
|
||||
masterTableId: [required],
|
||||
subJoinColumnId: [required],
|
||||
subJoinMany: [required],
|
||||
treeParentColumnId: [required],
|
||||
treeNameColumnId: [required]
|
||||
})
|
||||
|
||||
const tables = ref([]) // 表定义列表
|
||||
const menus = ref<any[]>([])
|
||||
const menuTreeProps = {
|
||||
label: 'name'
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.table,
|
||||
async (table) => {
|
||||
if (!table) return
|
||||
formData.value = table as any
|
||||
// 加载表列表
|
||||
if (table.dataSourceConfigId >= 0) {
|
||||
tables.value = await CodegenApi.getCodegenTableList(formData.value.dataSourceConfigId)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 加载菜单
|
||||
const resp = await MenuApi.getSimpleMenusList()
|
||||
menus.value = handleTree(resp)
|
||||
} catch {}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate: async () => unref(formRef)?.validate()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
import BasicInfoForm from './BasicInfoForm.vue'
|
||||
import ColumInfoForm from './ColumInfoForm.vue'
|
||||
import GenerateInfoForm from './GenerateInfoForm.vue'
|
||||
export { BasicInfoForm, ColumInfoForm, GenerateInfoForm }
|
||||
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<!-- 搜索 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input
|
||||
v-model="queryParams.tableName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入表名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input
|
||||
v-model="queryParams.tableComment"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入表描述"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['infra:codegen:create']" type="primary" @click="openImportTable()">
|
||||
<Icon class="mr-5px" icon="ep:zoom-in" />
|
||||
导入
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="数据源">
|
||||
<template #default="scope">
|
||||
{{
|
||||
dataSourceConfigList.find((config) => config.id === scope.row.dataSourceConfigId)?.name
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="表名称" prop="tableName" width="200" />
|
||||
<el-table-column
|
||||
:show-overflow-tooltip="true"
|
||||
align="center"
|
||||
label="表描述"
|
||||
prop="tableComment"
|
||||
width="200"
|
||||
/>
|
||||
<el-table-column align="center" label="实体" prop="className" width="200" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="更新时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="300px">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['infra:codegen:preview']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handlePreview(scope.row)"
|
||||
>
|
||||
预览
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['infra:codegen:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleUpdate(scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['infra:codegen:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['infra:codegen:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleSyncDB(scope.row)"
|
||||
>
|
||||
同步
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['infra:codegen:download']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleGenTable(scope.row)"
|
||||
>
|
||||
生成代码
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 弹窗:导入表 -->
|
||||
<ImportTable ref="importRef" @success="getList" />
|
||||
<!-- 弹窗:预览代码 -->
|
||||
<PreviewCode ref="previewRef" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
|
||||
import ImportTable from './ImportTable.vue'
|
||||
import PreviewCode from './PreviewCode.vue'
|
||||
|
||||
defineOptions({ name: 'InfraCodegen' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter() // 路由跳转
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tableName: undefined,
|
||||
tableComment: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const dataSourceConfigList = ref<DataSourceConfigApi.DataSourceConfigVO[]>([]) // 数据源列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CodegenApi.getCodegenTablePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 导入操作 */
|
||||
const importRef = ref()
|
||||
const openImportTable = () => {
|
||||
importRef.value.open()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const handleUpdate = (id: number) => {
|
||||
push('/codegen/edit?id=' + id)
|
||||
}
|
||||
|
||||
/** 预览操作 */
|
||||
const previewRef = ref()
|
||||
const handlePreview = (row: CodegenApi.CodegenTableVO) => {
|
||||
previewRef.value.open(row.id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await CodegenApi.deleteCodegenTable(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 同步操作 */
|
||||
const handleSyncDB = async (row: CodegenApi.CodegenTableVO) => {
|
||||
// 基于 DB 同步
|
||||
const tableName = row.tableName
|
||||
try {
|
||||
await message.confirm('确认要强制同步' + tableName + '表结构吗?', t('common.reminder'))
|
||||
await CodegenApi.syncCodegenFromDB(row.id)
|
||||
message.success('同步成功')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 生成代码操作 */
|
||||
const handleGenTable = async (row: CodegenApi.CodegenTableVO) => {
|
||||
const res = await CodegenApi.downloadCodegen(row.id)
|
||||
download.zip(res, 'codegen-' + row.className + '.zip')
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 加载数据源列表
|
||||
dataSourceConfigList.value = await DataSourceConfigApi.getDataSourceConfigList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="参数分类" prop="category">
|
||||
<el-input v-model="formData.category" placeholder="请输入参数分类" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参数名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入参数名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参数键名" prop="key">
|
||||
<el-input v-model="formData.key" placeholder="请输入参数键名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参数键值" prop="value">
|
||||
<el-input v-model="formData.value" placeholder="请输入参数键值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否可见" prop="visible">
|
||||
<el-radio-group v-model="formData.visible">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value as string"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraConfigForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
category: '',
|
||||
name: '',
|
||||
key: '',
|
||||
value: '',
|
||||
visible: true,
|
||||
remark: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
|
||||
key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
|
||||
value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }],
|
||||
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ConfigApi.getConfig(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as ConfigApi.ConfigVO
|
||||
if (formType.value === 'create') {
|
||||
await ConfigApi.createConfig(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ConfigApi.updateConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
category: '',
|
||||
name: '',
|
||||
key: '',
|
||||
value: '',
|
||||
visible: true,
|
||||
remark: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<!-- 搜索 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="参数名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入参数名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="参数键名" prop="key">
|
||||
<el-input
|
||||
v-model="queryParams.key"
|
||||
placeholder="请输入参数键名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="系统内置" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
placeholder="请选择系统内置"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['infra:config:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:config:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="参数主键" align="center" prop="id" />
|
||||
<el-table-column label="参数分类" align="center" prop="category" />
|
||||
<el-table-column label="参数名称" align="center" prop="name" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="参数键名" align="center" prop="key" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="参数键值" align="center" prop="value" />
|
||||
<el-table-column label="是否可见" align="center" prop="visible">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.visible" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="系统内置" align="center" prop="type">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_CONFIG_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['infra:config:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['infra:config:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ConfigForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
import ConfigForm from './ConfigForm.vue'
|
||||
|
||||
defineOptions({ name: 'InfraConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
key: undefined,
|
||||
type: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ConfigApi.getConfigPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ConfigApi.deleteConfig(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ConfigApi.exportConfig(queryParams)
|
||||
download.excel(data, '参数配置.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="数据源名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入参数名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据源连接" prop="url">
|
||||
<el-input v-model="formData.url" placeholder="请输入数据源连接" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="formData.username" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input v-model="formData.password" placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
|
||||
|
||||
defineOptions({ name: 'InfraDataSourceConfigForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<DataSourceConfigApi.DataSourceConfigVO>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
url: '',
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '数据源名称不能为空', trigger: 'blur' }],
|
||||
url: [{ required: true, message: '数据源连接不能为空', trigger: 'blur' }],
|
||||
username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await DataSourceConfigApi.getDataSourceConfig(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as DataSourceConfigApi.DataSourceConfigVO
|
||||
if (formType.value === 'create') {
|
||||
await DataSourceConfigApi.createDataSourceConfig(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await DataSourceConfigApi.updateDataSourceConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
url: '',
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form class="-mb-15px" :inline="true">
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['infra:data-source-config:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="主键编号" align="center" prop="id" />
|
||||
<el-table-column label="数据源名称" align="center" prop="name" />
|
||||
<el-table-column label="数据源连接" align="center" prop="url" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="用户名" align="center" prop="username" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['infra:data-source-config:update']"
|
||||
:disabled="scope.row.id === 0"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['infra:data-source-config:delete']"
|
||||
:disabled="scope.row.id === 0"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<DataSourceConfigForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
|
||||
import DataSourceConfigForm from './DataSourceConfigForm.vue'
|
||||
|
||||
defineOptions({ name: 'InfraDataSourceConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
list.value = await DataSourceConfigApi.getDataSourceConfigList()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await DataSourceConfigApi.deleteDataSourceConfig(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-radio-group v-model="formData.sex">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="出生年" prop="birthday">
|
||||
<el-date-picker
|
||||
v-model="formData.birthday"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择出生年"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="简介" prop="description">
|
||||
<Editor v-model="formData.description" height="150px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="头像" prop="avatar">
|
||||
<UploadImg v-model="formData.avatar" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import * as Demo01ContactApi from '@/api/infra/demo/demo01'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
sex: undefined,
|
||||
birthday: undefined,
|
||||
description: undefined,
|
||||
avatar: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
|
||||
birthday: [{ required: true, message: '出生年不能为空', trigger: 'blur' }],
|
||||
description: [{ required: true, message: '简介不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await Demo01ContactApi.getDemo01Contact(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as Demo01ContactApi.Demo01ContactVO
|
||||
if (formType.value === 'create') {
|
||||
await Demo01ContactApi.createDemo01Contact(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await Demo01ContactApi.updateDemo01Contact(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
sex: undefined,
|
||||
birthday: undefined,
|
||||
description: undefined,
|
||||
avatar: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-select v-model="queryParams.sex" placeholder="请选择性别" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['infra:demo01-contact:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:demo01-contact:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column label="性别" align="center" prop="sex">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="出生年"
|
||||
align="center"
|
||||
prop="birthday"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="简介" align="center" prop="description" />
|
||||
<el-table-column label="头像" align="center" prop="avatar" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['infra:demo01-contact:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['infra:demo01-contact:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<Demo01ContactForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as Demo01ContactApi from '@/api/infra/demo/demo01'
|
||||
import Demo01ContactForm from './Demo01ContactForm.vue'
|
||||
|
||||
defineOptions({ name: 'Demo01Contact' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
sex: null,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await Demo01ContactApi.getDemo01ContactPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await Demo01ContactApi.deleteDemo01Contact(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await Demo01ContactApi.exportDemo01Contact(queryParams)
|
||||
download.excel(data, '示例联系人.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="父级编号" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="formData.parentId"
|
||||
:data="demo02CategoryTree"
|
||||
:props="defaultProps"
|
||||
check-strictly
|
||||
default-expand-all
|
||||
placeholder="请选择父级编号"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as Demo02CategoryApi from '@/api/infra/demo/demo02'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
parentId: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
parentId: [{ required: true, message: '父级编号不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const demo02CategoryTree = ref() // 树形结构
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await Demo02CategoryApi.getDemo02Category(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
await getDemo02CategoryTree()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as Demo02CategoryApi.Demo02CategoryVO
|
||||
if (formType.value === 'create') {
|
||||
await Demo02CategoryApi.createDemo02Category(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await Demo02CategoryApi.updateDemo02Category(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
parentId: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 获得示例分类树 */
|
||||
const getDemo02CategoryTree = async () => {
|
||||
demo02CategoryTree.value = []
|
||||
const data = await Demo02CategoryApi.getDemo02CategoryList()
|
||||
const root: Tree = { id: 0, name: '顶级示例分类', children: [] }
|
||||
root.children = handleTree(data, 'id', 'parentId')
|
||||
demo02CategoryTree.value.push(root)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['infra:demo02-category:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:demo02-category:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
<el-button type="danger" plain @click="toggleExpandAll">
|
||||
<Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
row-key="id"
|
||||
:default-expand-all="isExpandAll"
|
||||
v-if="refreshTable"
|
||||
>
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['infra:demo02-category:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['infra:demo02-category:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<Demo02CategoryForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import download from '@/utils/download'
|
||||
import * as Demo02CategoryApi from '@/api/infra/demo/demo02'
|
||||
import Demo02CategoryForm from './Demo02CategoryForm.vue'
|
||||
|
||||
defineOptions({ name: 'Demo02Category' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
name: null,
|
||||
parentId: null,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await Demo02CategoryApi.getDemo02CategoryList(queryParams)
|
||||
list.value = handleTree(data, 'id', 'parentId')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await Demo02CategoryApi.deleteDemo02Category(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await Demo02CategoryApi.exportDemo02Category(queryParams)
|
||||
download.excel(data, '示例分类.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const isExpandAll = ref(true) // 是否展开,默认全部展开
|
||||
const refreshTable = ref(true) // 重新渲染表格状态
|
||||
const toggleExpandAll = async () => {
|
||||
refreshTable.value = false
|
||||
isExpandAll.value = !isExpandAll.value
|
||||
await nextTick()
|
||||
refreshTable.value = true
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-radio-group v-model="formData.sex">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="出生日期" prop="birthday">
|
||||
<el-date-picker
|
||||
v-model="formData.birthday"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择出生日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="简介" prop="description">
|
||||
<Editor v-model="formData.description" height="150px" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
sex: undefined,
|
||||
birthday: undefined,
|
||||
description: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
|
||||
birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],
|
||||
description: [{ required: true, message: '简介不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await Demo03StudentApi.getDemo03Student(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as Demo03StudentApi.Demo03StudentVO
|
||||
if (formType.value === 'create') {
|
||||
await Demo03StudentApi.createDemo03Student(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await Demo03StudentApi.updateDemo03Student(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
sex: undefined,
|
||||
birthday: undefined,
|
||||
description: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分数" prop="score">
|
||||
<el-input v-model="formData.score" placeholder="请输入分数" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
studentId: undefined,
|
||||
name: undefined,
|
||||
score: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
score: [{ required: true, message: '分数不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number, studentId: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
formData.value.studentId = studentId
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await Demo03StudentApi.getDemo03Course(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value
|
||||
if (formType.value === 'create') {
|
||||
await Demo03StudentApi.createDemo03Course(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await Demo03StudentApi.updateDemo03Course(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
studentId: undefined,
|
||||
name: undefined,
|
||||
score: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-button
|
||||
v-hasPermi="['infra:demo03-student:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" label="编号" prop="id" />
|
||||
<el-table-column align="center" label="名字" prop="name" />
|
||||
<el-table-column align="center" label="分数" prop="score" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['infra:demo03-student:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['infra:demo03-student:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<Demo03CourseForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
|
||||
import Demo03CourseForm from './Demo03CourseForm.vue'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps<{
|
||||
studentId?: number // 学生编号(主表的关联字段)
|
||||
}>()
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
studentId: undefined as unknown
|
||||
})
|
||||
|
||||
/** 监听主表的关联字段的变化,加载对应的子表数据 */
|
||||
watch(
|
||||
() => props.studentId,
|
||||
(val: number) => {
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
queryParams.studentId = val
|
||||
handleQuery()
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await Demo03StudentApi.getDemo03CoursePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
if (!props.studentId) {
|
||||
message.error('请选择一个学生')
|
||||
return
|
||||
}
|
||||
formRef.value.open(type, id, props.studentId)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await Demo03StudentApi.deleteDemo03Course(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="班主任" prop="teacher">
|
||||
<el-input v-model="formData.teacher" placeholder="请输入班主任" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
studentId: undefined,
|
||||
name: undefined,
|
||||
teacher: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
teacher: [{ required: true, message: '班主任不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number, studentId: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
formData.value.studentId = studentId
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await Demo03StudentApi.getDemo03Grade(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value
|
||||
if (formType.value === 'create') {
|
||||
await Demo03StudentApi.createDemo03Grade(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await Demo03StudentApi.updateDemo03Grade(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
studentId: undefined,
|
||||
name: undefined,
|
||||
teacher: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-button
|
||||
v-hasPermi="['infra:demo03-student:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" label="编号" prop="id" />
|
||||
<el-table-column align="center" label="名字" prop="name" />
|
||||
<el-table-column align="center" label="班主任" prop="teacher" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['infra:demo03-student:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['infra:demo03-student:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<Demo03GradeForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
|
||||
import Demo03GradeForm from './Demo03GradeForm.vue'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps<{
|
||||
studentId?: number // 学生编号(主表的关联字段)
|
||||
}>()
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
studentId: undefined as unknown
|
||||
})
|
||||
|
||||
/** 监听主表的关联字段的变化,加载对应的子表数据 */
|
||||
watch(
|
||||
() => props.studentId,
|
||||
(val: number) => {
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
queryParams.studentId = val
|
||||
handleQuery()
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await Demo03StudentApi.getDemo03GradePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
if (!props.studentId) {
|
||||
message.error('请选择一个学生')
|
||||
return
|
||||
}
|
||||
formRef.value.open(type, id, props.studentId)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await Demo03StudentApi.deleteDemo03Grade(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入名字"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-select v-model="queryParams.sex" class="!w-240px" clearable placeholder="请选择性别">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['infra:demo03-student:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['infra:demo03-student:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
:stripe="true"
|
||||
highlight-current-row
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<el-table-column align="center" label="编号" prop="id" />
|
||||
<el-table-column align="center" label="名字" prop="name" />
|
||||
<el-table-column align="center" label="性别" prop="sex">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="出生日期"
|
||||
prop="birthday"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="简介" prop="description" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['infra:demo03-student:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['infra:demo03-student:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<Demo03StudentForm ref="formRef" @success="getList" />
|
||||
<!-- 子表的列表 -->
|
||||
<ContentWrap>
|
||||
<el-tabs model-value="demo03Course">
|
||||
<el-tab-pane label="学生课程" name="demo03Course">
|
||||
<Demo03CourseList :student-id="currentRow?.id" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="学生班级" name="demo03Grade">
|
||||
<Demo03GradeList :student-id="currentRow?.id" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
|
||||
import Demo03StudentForm from './Demo03StudentForm.vue'
|
||||
import Demo03CourseList from './components/Demo03CourseList.vue'
|
||||
import Demo03GradeList from './components/Demo03GradeList.vue'
|
||||
|
||||
defineOptions({ name: 'Demo03Student' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
sex: null,
|
||||
description: null,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await Demo03StudentApi.getDemo03StudentPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await Demo03StudentApi.deleteDemo03Student(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await Demo03StudentApi.exportDemo03Student(queryParams)
|
||||
download.excel(data, '学生.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 选中行操作 */
|
||||
const currentRow = ref({}) // 选中行
|
||||
const handleCurrentChange = (row) => {
|
||||
currentRow.value = row
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-radio-group v-model="formData.sex">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="出生日期" prop="birthday">
|
||||
<el-date-picker
|
||||
v-model="formData.birthday"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择出生日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="简介" prop="description">
|
||||
<Editor v-model="formData.description" height="150px" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 子表的表单 -->
|
||||
<el-tabs v-model="subTabsName">
|
||||
<el-tab-pane label="学生课程" name="demo03Course">
|
||||
<Demo03CourseForm ref="demo03CourseFormRef" :student-id="formData.id" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="学生班级" name="demo03Grade">
|
||||
<Demo03GradeForm ref="demo03GradeFormRef" :student-id="formData.id" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
|
||||
import Demo03CourseForm from './components/Demo03CourseForm.vue'
|
||||
import Demo03GradeForm from './components/Demo03GradeForm.vue'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
sex: undefined,
|
||||
birthday: undefined,
|
||||
description: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
|
||||
birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],
|
||||
description: [{ required: true, message: '简介不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 子表的表单 */
|
||||
const subTabsName = ref('demo03Course')
|
||||
const demo03CourseFormRef = ref()
|
||||
const demo03GradeFormRef = ref()
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await Demo03StudentApi.getDemo03Student(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 校验子表单
|
||||
try {
|
||||
await demo03CourseFormRef.value.validate()
|
||||
} catch (e) {
|
||||
subTabsName.value = 'demo03Course'
|
||||
return
|
||||
}
|
||||
try {
|
||||
await demo03GradeFormRef.value.validate()
|
||||
} catch (e) {
|
||||
subTabsName.value = 'demo03Grade'
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as Demo03StudentApi.Demo03StudentVO
|
||||
// 拼接子表的数据
|
||||
data.demo03Courses = demo03CourseFormRef.value.getData()
|
||||
data.demo03Grade = demo03GradeFormRef.value.getData()
|
||||
if (formType.value === 'create') {
|
||||
await Demo03StudentApi.createDemo03Student(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await Demo03StudentApi.updateDemo03Student(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
sex: undefined,
|
||||
birthday: undefined,
|
||||
description: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
v-loading="formLoading"
|
||||
label-width="0px"
|
||||
:inline-message="true"
|
||||
>
|
||||
<el-table :data="formData" class="-mt-10px">
|
||||
<el-table-column label="序号" type="index" width="100" />
|
||||
<el-table-column label="名字" min-width="150">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.name`" :rules="formRules.name" class="mb-0px!">
|
||||
<el-input v-model="row.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分数" min-width="150">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.score`" :rules="formRules.score" class="mb-0px!">
|
||||
<el-input v-model="row.score" placeholder="请输入分数" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="60">
|
||||
<template #default="{ $index }">
|
||||
<el-button @click="handleDelete($index)" link>—</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
<el-row justify="center" class="mt-3">
|
||||
<el-button @click="handleAdd" round>+ 添加学生课程</el-button>
|
||||
</el-row>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
|
||||
|
||||
const props = defineProps<{
|
||||
studentId: undefined // 学生编号(主表的关联字段)
|
||||
}>()
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref([])
|
||||
const formRules = reactive({
|
||||
studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
score: [{ required: true, message: '分数不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 监听主表的关联字段的变化,加载对应的子表数据 */
|
||||
watch(
|
||||
() => props.studentId,
|
||||
async (val) => {
|
||||
// 1. 重置表单
|
||||
formData.value = []
|
||||
// 2. val 非空,则加载数据
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
formLoading.value = true
|
||||
formData.value = await Demo03StudentApi.getDemo03CourseListByStudentId(val)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
const row = {
|
||||
id: undefined,
|
||||
studentId: undefined,
|
||||
name: undefined,
|
||||
score: undefined
|
||||
}
|
||||
row.studentId = props.studentId
|
||||
formData.value.push(row)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = (index) => {
|
||||
formData.value.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = () => {
|
||||
return formRef.value.validate()
|
||||
}
|
||||
|
||||
/** 表单值 */
|
||||
const getData = () => {
|
||||
return formData.value
|
||||
}
|
||||
|
||||
defineExpose({ validate, getData })
|
||||
</script>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column label="分数" align="center" prop="score" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps<{
|
||||
studentId: undefined // 学生编号(主表的关联字段)
|
||||
}>()
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
list.value = await Demo03StudentApi.getDemo03CourseListByStudentId(props.studentId)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="班主任" prop="teacher">
|
||||
<el-input v-model="formData.teacher" placeholder="请输入班主任" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
|
||||
|
||||
const props = defineProps<{
|
||||
studentId: undefined // 学生编号(主表的关联字段)
|
||||
}>()
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref([])
|
||||
const formRules = reactive({
|
||||
studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
teacher: [{ required: true, message: '班主任不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 监听主表的关联字段的变化,加载对应的子表数据 */
|
||||
watch(
|
||||
() => props.studentId,
|
||||
async (val) => {
|
||||
// 1. 重置表单
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
studentId: undefined,
|
||||
name: undefined,
|
||||
teacher: undefined
|
||||
}
|
||||
// 2. val 非空,则加载数据
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
formLoading.value = true
|
||||
const data = await Demo03StudentApi.getDemo03GradeByStudentId(val)
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
formData.value = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = () => {
|
||||
return formRef.value.validate()
|
||||
}
|
||||
|
||||
/** 表单值 */
|
||||
const getData = () => {
|
||||
return formData.value
|
||||
}
|
||||
|
||||
defineExpose({ validate, getData })
|
||||
</script>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column label="班主任" align="center" prop="teacher" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps<{
|
||||
studentId: undefined // 学生编号(主表的关联字段)
|
||||
}>()
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await Demo03StudentApi.getDemo03GradeByStudentId(props.studentId)
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
list.value.push(data)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-select v-model="queryParams.sex" placeholder="请选择性别" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['infra:demo03-student:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:demo03-student:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<!-- 子表的列表 -->
|
||||
<el-table-column type="expand">
|
||||
<template #default="scope">
|
||||
<el-tabs model-value="demo03Course">
|
||||
<el-tab-pane label="学生课程" name="demo03Course">
|
||||
<Demo03CourseList :student-id="scope.row.id" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="学生班级" name="demo03Grade">
|
||||
<Demo03GradeList :student-id="scope.row.id" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column label="性别" align="center" prop="sex">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="出生日期"
|
||||
align="center"
|
||||
prop="birthday"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="简介" align="center" prop="description" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['infra:demo03-student:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['infra:demo03-student:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<Demo03StudentForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
|
||||
import Demo03StudentForm from './Demo03StudentForm.vue'
|
||||
import Demo03CourseList from './components/Demo03CourseList.vue'
|
||||
import Demo03GradeList from './components/Demo03GradeList.vue'
|
||||
|
||||
defineOptions({ name: 'Demo03Student' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
sex: null,
|
||||
description: null,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await Demo03StudentApi.getDemo03StudentPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await Demo03StudentApi.deleteDemo03Student(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await Demo03StudentApi.exportDemo03Student(queryParams)
|
||||
download.excel(data, '学生.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-radio-group v-model="formData.sex">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="出生日期" prop="birthday">
|
||||
<el-date-picker
|
||||
v-model="formData.birthday"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择出生日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="简介" prop="description">
|
||||
<Editor v-model="formData.description" height="150px" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 子表的表单 -->
|
||||
<el-tabs v-model="subTabsName">
|
||||
<el-tab-pane label="学生课程" name="demo03Course">
|
||||
<Demo03CourseForm ref="demo03CourseFormRef" :student-id="formData.id" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="学生班级" name="demo03Grade">
|
||||
<Demo03GradeForm ref="demo03GradeFormRef" :student-id="formData.id" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'
|
||||
import Demo03CourseForm from './components/Demo03CourseForm.vue'
|
||||
import Demo03GradeForm from './components/Demo03GradeForm.vue'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
sex: undefined,
|
||||
birthday: undefined,
|
||||
description: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
|
||||
birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],
|
||||
description: [{ required: true, message: '简介不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 子表的表单 */
|
||||
const subTabsName = ref('demo03Course')
|
||||
const demo03CourseFormRef = ref()
|
||||
const demo03GradeFormRef = ref()
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await Demo03StudentApi.getDemo03Student(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 校验子表单
|
||||
try {
|
||||
await demo03CourseFormRef.value.validate()
|
||||
} catch (e) {
|
||||
subTabsName.value = 'demo03Course'
|
||||
return
|
||||
}
|
||||
try {
|
||||
await demo03GradeFormRef.value.validate()
|
||||
} catch (e) {
|
||||
subTabsName.value = 'demo03Grade'
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as Demo03StudentApi.Demo03StudentVO
|
||||
// 拼接子表的数据
|
||||
data.demo03Courses = demo03CourseFormRef.value.getData()
|
||||
data.demo03Grade = demo03GradeFormRef.value.getData()
|
||||
if (formType.value === 'create') {
|
||||
await Demo03StudentApi.createDemo03Student(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await Demo03StudentApi.updateDemo03Student(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
sex: undefined,
|
||||
birthday: undefined,
|
||||
description: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
v-loading="formLoading"
|
||||
label-width="0px"
|
||||
:inline-message="true"
|
||||
>
|
||||
<el-table :data="formData" class="-mt-10px">
|
||||
<el-table-column label="序号" type="index" width="100" />
|
||||
<el-table-column label="名字" min-width="150">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.name`" :rules="formRules.name" class="mb-0px!">
|
||||
<el-input v-model="row.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分数" min-width="150">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item :prop="`${$index}.score`" :rules="formRules.score" class="mb-0px!">
|
||||
<el-input v-model="row.score" placeholder="请输入分数" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="60">
|
||||
<template #default="{ $index }">
|
||||
<el-button @click="handleDelete($index)" link>—</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
<el-row justify="center" class="mt-3">
|
||||
<el-button @click="handleAdd" round>+ 添加学生课程</el-button>
|
||||
</el-row>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'
|
||||
|
||||
const props = defineProps<{
|
||||
studentId: undefined // 学生编号(主表的关联字段)
|
||||
}>()
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref([])
|
||||
const formRules = reactive({
|
||||
studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
score: [{ required: true, message: '分数不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 监听主表的关联字段的变化,加载对应的子表数据 */
|
||||
watch(
|
||||
() => props.studentId,
|
||||
async (val) => {
|
||||
// 1. 重置表单
|
||||
formData.value = []
|
||||
// 2. val 非空,则加载数据
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
formLoading.value = true
|
||||
formData.value = await Demo03StudentApi.getDemo03CourseListByStudentId(val)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
const row = {
|
||||
id: undefined,
|
||||
studentId: undefined,
|
||||
name: undefined,
|
||||
score: undefined
|
||||
}
|
||||
row.studentId = props.studentId
|
||||
formData.value.push(row)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = (index) => {
|
||||
formData.value.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = () => {
|
||||
return formRef.value.validate()
|
||||
}
|
||||
|
||||
/** 表单值 */
|
||||
const getData = () => {
|
||||
return formData.value
|
||||
}
|
||||
|
||||
defineExpose({ validate, getData })
|
||||
</script>
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="班主任" prop="teacher">
|
||||
<el-input v-model="formData.teacher" placeholder="请输入班主任" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'
|
||||
|
||||
const props = defineProps<{
|
||||
studentId: undefined // 学生编号(主表的关联字段)
|
||||
}>()
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref([])
|
||||
const formRules = reactive({
|
||||
studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
teacher: [{ required: true, message: '班主任不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 监听主表的关联字段的变化,加载对应的子表数据 */
|
||||
watch(
|
||||
() => props.studentId,
|
||||
async (val) => {
|
||||
// 1. 重置表单
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
studentId: undefined,
|
||||
name: undefined,
|
||||
teacher: undefined
|
||||
}
|
||||
// 2. val 非空,则加载数据
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
formLoading.value = true
|
||||
const data = await Demo03StudentApi.getDemo03GradeByStudentId(val)
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
formData.value = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = () => {
|
||||
return formRef.value.validate()
|
||||
}
|
||||
|
||||
/** 表单值 */
|
||||
const getData = () => {
|
||||
return formData.value
|
||||
}
|
||||
|
||||
defineExpose({ validate, getData })
|
||||
</script>
|
||||
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-select v-model="queryParams.sex" placeholder="请选择性别" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['infra:demo03-student:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:demo03-student:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column label="性别" align="center" prop="sex">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="出生日期"
|
||||
align="center"
|
||||
prop="birthday"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="简介" align="center" prop="description" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['infra:demo03-student:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['infra:demo03-student:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<Demo03StudentForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'
|
||||
import Demo03StudentForm from './Demo03StudentForm.vue'
|
||||
|
||||
defineOptions({ name: 'Demo03Student' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
sex: null,
|
||||
description: null,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await Demo03StudentApi.getDemo03StudentPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await Demo03StudentApi.deleteDemo03Student(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await Demo03StudentApi.exportDemo03Student(queryParams)
|
||||
download.excel(data, '学生.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<IFrame v-if="!loading" :src="url" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraDruid' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const url = ref(import.meta.env.VITE_BASE_URL + '/druid/index.html')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.druid')
|
||||
if (data && data.length > 0) {
|
||||
url.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="上传文件">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:action="uploadUrl"
|
||||
:auto-upload="false"
|
||||
:data="data"
|
||||
:disabled="formLoading"
|
||||
:limit="1"
|
||||
:on-change="handleFileChange"
|
||||
:on-error="submitFormError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitFormSuccess"
|
||||
:http-request="httpRequest"
|
||||
accept=".jpg, .png, .gif"
|
||||
drag
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip" style="color: red">
|
||||
提示:仅允许导入 jpg、png、gif 格式文件!
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||
|
||||
defineOptions({ name: 'InfraFileForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const fileList = ref([]) // 文件列表
|
||||
const data = ref({ path: '' })
|
||||
const uploadRef = ref()
|
||||
|
||||
const { uploadUrl, httpRequest } = useUpload()
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 处理上传的文件发生变化 */
|
||||
const handleFileChange = (file) => {
|
||||
data.value.path = file.name
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const submitFileForm = () => {
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
unref(uploadRef)?.submit()
|
||||
}
|
||||
|
||||
/** 文件上传成功处理 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitFormSuccess = () => {
|
||||
// 清理
|
||||
dialogVisible.value = false
|
||||
formLoading.value = false
|
||||
unref(uploadRef)?.clearFiles()
|
||||
// 提示成功,并刷新
|
||||
message.success(t('common.createSuccess'))
|
||||
emit('success')
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('上传失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<!-- 搜索 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="文件路径" prop="path">
|
||||
<el-input
|
||||
v-model="queryParams.path"
|
||||
placeholder="请输入文件路径"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件类型" prop="type" width="80">
|
||||
<el-input
|
||||
v-model="queryParams.type"
|
||||
placeholder="请输入文件类型"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button type="primary" plain @click="openForm">
|
||||
<Icon icon="ep:upload" class="mr-5px" /> 上传文件
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="文件名" align="center" prop="name" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="文件路径" align="center" prop="path" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="URL" align="center" prop="url" :show-overflow-tooltip="true" />
|
||||
<el-table-column
|
||||
label="文件大小"
|
||||
align="center"
|
||||
prop="size"
|
||||
width="120"
|
||||
:formatter="fileSizeFormatter"
|
||||
/>
|
||||
<el-table-column label="文件类型" align="center" prop="type" width="180px" />
|
||||
<el-table-column label="文件内容" align="center" prop="url" width="110px">
|
||||
<template #default="{ row }">
|
||||
<el-image
|
||||
v-if="row.type.includes('image')"
|
||||
class="h-80px w-80px"
|
||||
lazy
|
||||
:src="row.url"
|
||||
:preview-src-list="[row.url]"
|
||||
preview-teleported
|
||||
fit="cover"
|
||||
/>
|
||||
<el-link
|
||||
v-else-if="row.type.includes('pdf')"
|
||||
type="primary"
|
||||
:href="row.url"
|
||||
:underline="false"
|
||||
target="_blank"
|
||||
>预览</el-link
|
||||
>
|
||||
<el-link v-else type="primary" download :href="row.url" :underline="false" target="_blank"
|
||||
>下载</el-link
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="上传时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['infra:file:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<FileForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { fileSizeFormatter } from '@/utils'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as FileApi from '@/api/infra/file'
|
||||
import FileForm from './FileForm.vue'
|
||||
|
||||
defineOptions({ name: 'InfraFile' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
path: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await FileApi.getFilePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = () => {
|
||||
formRef.value.open()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await FileApi.deleteFile(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="配置名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入配置名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
<el-form-item label="存储器" prop="storage">
|
||||
<el-select
|
||||
v-model="formData.storage"
|
||||
:disabled="formData.id !== undefined"
|
||||
placeholder="请选择存储器"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="parseInt(dict.value)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- DB -->
|
||||
<!-- Local / FTP / SFTP -->
|
||||
<el-form-item
|
||||
v-if="formData.storage >= 10 && formData.storage <= 12"
|
||||
label="基础路径"
|
||||
prop="config.basePath"
|
||||
>
|
||||
<el-input v-model="formData.config.basePath" placeholder="请输入基础路径" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.storage >= 11 && formData.storage <= 12"
|
||||
label="主机地址"
|
||||
prop="config.host"
|
||||
>
|
||||
<el-input v-model="formData.config.host" placeholder="请输入主机地址" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.storage >= 11 && formData.storage <= 12"
|
||||
label="主机端口"
|
||||
prop="config.port"
|
||||
>
|
||||
<el-input-number v-model="formData.config.port" :min="0" placeholder="请输入主机端口" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.storage >= 11 && formData.storage <= 12"
|
||||
label="用户名"
|
||||
prop="config.username"
|
||||
>
|
||||
<el-input v-model="formData.config.username" placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.storage >= 11 && formData.storage <= 12"
|
||||
label="密码"
|
||||
prop="config.password"
|
||||
>
|
||||
<el-input v-model="formData.config.password" placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.storage === 11" label="连接模式" prop="config.mode">
|
||||
<el-radio-group v-model="formData.config.mode">
|
||||
<el-radio key="Active" label="Active">主动模式</el-radio>
|
||||
<el-radio key="Passive" label="Passive">被动模式</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- S3 -->
|
||||
<el-form-item v-if="formData.storage === 20" label="节点地址" prop="config.endpoint">
|
||||
<el-input v-model="formData.config.endpoint" placeholder="请输入节点地址" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.storage === 20" label="存储 bucket" prop="config.bucket">
|
||||
<el-input v-model="formData.config.bucket" placeholder="请输入 bucket" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.storage === 20" label="accessKey" prop="config.accessKey">
|
||||
<el-input v-model="formData.config.accessKey" placeholder="请输入 accessKey" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.storage === 20" label="accessSecret" prop="config.accessSecret">
|
||||
<el-input v-model="formData.config.accessSecret" placeholder="请输入 accessSecret" />
|
||||
</el-form-item>
|
||||
<!-- 通用 -->
|
||||
<el-form-item v-if="formData.storage === 20" label="自定义域名">
|
||||
<!-- 无需参数校验,所以去掉 prop -->
|
||||
<el-input v-model="formData.config.domain" placeholder="请输入自定义域名" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else-if="formData.storage" label="自定义域名" prop="config.domain">
|
||||
<el-input v-model="formData.config.domain" placeholder="请输入自定义域名" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
|
||||
import * as FileConfigApi from '@/api/infra/fileConfig'
|
||||
import { FormRules } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'InfraFileConfigForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
storage: 0,
|
||||
remark: '',
|
||||
config: {} as FileConfigApi.FileClientConfig
|
||||
})
|
||||
const formRules = reactive<FormRules>({
|
||||
name: [{ required: true, message: '配置名不能为空', trigger: 'blur' }],
|
||||
storage: [{ required: true, message: '存储器不能为空', trigger: 'change' }],
|
||||
config: {
|
||||
basePath: [{ required: true, message: '基础路径不能为空', trigger: 'blur' }],
|
||||
host: [{ required: true, message: '主机地址不能为空', trigger: 'blur' }],
|
||||
port: [{ required: true, message: '主机端口不能为空', trigger: 'blur' }],
|
||||
username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
|
||||
mode: [{ required: true, message: '连接模式不能为空', trigger: 'change' }],
|
||||
endpoint: [{ required: true, message: '节点地址不能为空', trigger: 'blur' }],
|
||||
bucket: [{ required: true, message: '存储 bucket 不能为空', trigger: 'blur' }],
|
||||
accessKey: [{ required: true, message: 'accessKey 不能为空', trigger: 'blur' }],
|
||||
accessSecret: [{ required: true, message: 'accessSecret 不能为空', trigger: 'blur' }],
|
||||
domain: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }]
|
||||
} as FormRules
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await FileConfigApi.getFileConfig(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as FileConfigApi.FileConfigVO
|
||||
if (formType.value === 'create') {
|
||||
await FileConfigApi.createFileConfig(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await FileConfigApi.updateFileConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
storage: undefined!,
|
||||
remark: '',
|
||||
config: {} as FileConfigApi.FileClientConfig
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<!-- 搜索 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="配置名" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入配置名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="存储器" prop="storage">
|
||||
<el-select
|
||||
v-model="queryParams.storage"
|
||||
placeholder="请选择存储器"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['infra:file-config:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="配置名" align="center" prop="name" />
|
||||
<el-table-column label="存储器" align="center" prop="storage">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="scope.row.storage" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="主配置" align="center" prop="primary">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" width="240px">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['infra:file-config:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
:disabled="scope.row.master"
|
||||
@click="handleMaster(scope.row.id)"
|
||||
v-hasPermi="['infra:file-config:update']"
|
||||
>
|
||||
主配置
|
||||
</el-button>
|
||||
<el-button link type="primary" @click="handleTest(scope.row.id)"> 测试 </el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['infra:file-config:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<FileConfigForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as FileConfigApi from '@/api/infra/fileConfig'
|
||||
import FileConfigForm from './FileConfigForm.vue'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
|
||||
defineOptions({ name: 'InfraFileConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
storage: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await FileConfigApi.getFileConfigPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await FileConfigApi.deleteFileConfig(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 主配置按钮操作 */
|
||||
const handleMaster = async (id) => {
|
||||
try {
|
||||
await message.confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?')
|
||||
await FileConfigApi.updateFileConfigMaster(id)
|
||||
message.success(t('common.updateSuccess'))
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 测试按钮操作 */
|
||||
const handleTest = async (id) => {
|
||||
try {
|
||||
const response = await FileConfigApi.testFileConfig(id)
|
||||
message.alert('测试通过,上传文件成功!访问地址:' + response)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="任务详细" width="700px">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="任务编号" min-width="60">
|
||||
{{ detailData.id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">
|
||||
{{ detailData.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="detailData.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="处理器的名字">
|
||||
{{ detailData.handlerName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="处理器的参数">
|
||||
{{ detailData.handlerParam }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="Cron 表达式">
|
||||
{{ detailData.cronExpression }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="重试次数">
|
||||
{{ detailData.retryCount }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="重试间隔">
|
||||
{{ detailData.retryInterval + ' 毫秒' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="监控超时时间">
|
||||
{{ detailData.monitorTimeout > 0 ? detailData.monitorTimeout + ' 毫秒' : '未开启' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="后续执行时间">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(nextTime, index) in nextTimes"
|
||||
:key="index"
|
||||
:timestamp="formatDate(nextTime)"
|
||||
>
|
||||
第 {{ index + 1 }} 次
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as JobApi from '@/api/infra/job'
|
||||
|
||||
defineOptions({ name: 'InfraJobDetail' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref({} as JobApi.JobVO) // 详情数据
|
||||
const nextTimes = ref([]) // 下一轮执行时间的数组
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
// 查看,设置数据
|
||||
if (id) {
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detailData.value = await JobApi.getJob(id)
|
||||
// 获取下一次执行时间
|
||||
nextTimes.value = await JobApi.getJobNextTimes(id)
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="任务名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入任务名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="处理器的名字" prop="handlerName">
|
||||
<el-input
|
||||
:readonly="formData.id !== undefined"
|
||||
v-model="formData.handlerName"
|
||||
placeholder="请输入处理器的名字"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="处理器的参数" prop="handlerParam">
|
||||
<el-input v-model="formData.handlerParam" placeholder="请输入处理器的参数" />
|
||||
</el-form-item>
|
||||
<el-form-item label="CRON 表达式" prop="cronExpression">
|
||||
<crontab v-model="formData.cronExpression" />
|
||||
</el-form-item>
|
||||
<el-form-item label="重试次数" prop="retryCount">
|
||||
<el-input
|
||||
v-model="formData.retryCount"
|
||||
placeholder="请输入重试次数。设置为 0 时,不进行重试"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="重试间隔" prop="retryInterval">
|
||||
<el-input
|
||||
v-model="formData.retryInterval"
|
||||
placeholder="请输入重试间隔,单位:毫秒。设置为 0 时,无需间隔"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="监控超时时间" prop="monitorTimeout">
|
||||
<el-input v-model="formData.monitorTimeout" placeholder="请输入监控超时时间,单位:毫秒" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="submitForm" :loading="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as JobApi from '@/api/infra/job'
|
||||
|
||||
defineOptions({ name: 'JobForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
handlerName: '',
|
||||
handlerParam: '',
|
||||
cronExpression: '',
|
||||
retryCount: undefined,
|
||||
retryInterval: undefined,
|
||||
monitorTimeout: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
|
||||
handlerName: [{ required: true, message: '处理器的名字不能为空', trigger: 'blur' }],
|
||||
cronExpression: [{ required: true, message: 'CRON 表达式不能为空', trigger: 'blur' }],
|
||||
retryCount: [{ required: true, message: '重试次数不能为空', trigger: 'blur' }],
|
||||
retryInterval: [{ required: true, message: '重试间隔不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await JobApi.getJob(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交按钮 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as JobApi.JobVO
|
||||
if (formType.value === 'create') {
|
||||
await JobApi.createJob(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await JobApi.updateJob(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
handlerName: '',
|
||||
handlerParam: '',
|
||||
cronExpression: '',
|
||||
retryCount: undefined,
|
||||
retryInterval: undefined,
|
||||
monitorTimeout: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="任务名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入任务名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择任务状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_JOB_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="处理器的名字" prop="handlerName">
|
||||
<el-input
|
||||
v-model="queryParams.handlerName"
|
||||
placeholder="请输入处理器的名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['infra:job:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:job:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
<el-button type="info" plain @click="handleJobLog()" v-hasPermi="['infra:job:query']">
|
||||
<Icon icon="ep:zoom-in" class="mr-5px" /> 执行日志
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="任务编号" align="center" prop="id" />
|
||||
<el-table-column label="任务名称" align="center" prop="name" />
|
||||
<el-table-column label="任务状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="处理器的名字" align="center" prop="handlerName" />
|
||||
<el-table-column label="处理器的参数" align="center" prop="handlerParam" />
|
||||
<el-table-column label="CRON 表达式" align="center" prop="cronExpression" />
|
||||
<el-table-column label="操作" align="center" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['infra:job:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleChangeStatus(scope.row)"
|
||||
v-hasPermi="['infra:job:update']"
|
||||
>
|
||||
{{ scope.row.status === InfraJobStatusEnum.STOP ? '开启' : '暂停' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['infra:job:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
@command="(command) => handleCommand(command, scope.row)"
|
||||
v-hasPermi="['infra:job:trigger', 'infra:job:query']"
|
||||
>
|
||||
<el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="handleRun" v-if="checkPermi(['infra:job:trigger'])">
|
||||
执行一次
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="openDetail" v-if="checkPermi(['infra:job:query'])">
|
||||
任务详细
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="handleJobLog" v-if="checkPermi(['infra:job:query'])">
|
||||
调度日志
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<JobForm ref="formRef" @success="getList" />
|
||||
<!-- 表单弹窗:查看 -->
|
||||
<JobDetail ref="detailRef" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import JobForm from './JobForm.vue'
|
||||
import JobDetail from './JobDetail.vue'
|
||||
import download from '@/utils/download'
|
||||
import * as JobApi from '@/api/infra/job'
|
||||
import { InfraJobStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'InfraJob' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
handlerName: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await JobApi.getJobPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await JobApi.exportJob(queryParams)
|
||||
download.excel(data, '定时任务.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 修改状态操作 */
|
||||
const handleChangeStatus = async (row: JobApi.JobVO) => {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const text = row.status === InfraJobStatusEnum.STOP ? '开启' : '关闭'
|
||||
await message.confirm(
|
||||
'确认要' + text + '定时任务编号为"' + row.id + '"的数据项?',
|
||||
t('common.reminder')
|
||||
)
|
||||
const status =
|
||||
row.status === InfraJobStatusEnum.STOP ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP
|
||||
await JobApi.updateJobStatus(row.id, status)
|
||||
message.success(text + '成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {
|
||||
// 取消后,进行恢复按钮
|
||||
row.status =
|
||||
row.status === InfraJobStatusEnum.NORMAL ? InfraJobStatusEnum.STOP : InfraJobStatusEnum.NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await JobApi.deleteJob(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** '更多'操作按钮 */
|
||||
const handleCommand = (command, row) => {
|
||||
switch (command) {
|
||||
case 'handleRun':
|
||||
handleRun(row)
|
||||
break
|
||||
case 'openDetail':
|
||||
openDetail(row.id)
|
||||
break
|
||||
case 'handleJobLog':
|
||||
handleJobLog(row?.id)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 执行一次 */
|
||||
const handleRun = async (row: JobApi.JobVO) => {
|
||||
try {
|
||||
// 二次确认
|
||||
await message.confirm('确认要立即执行一次' + row.name + '?', t('common.reminder'))
|
||||
// 提交执行
|
||||
await JobApi.runJob(row.id)
|
||||
message.success('执行成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 查看操作 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (id: number) => {
|
||||
detailRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 跳转执行日志 */
|
||||
const handleJobLog = (id?: number) => {
|
||||
if (id && id > 0) {
|
||||
push('/job/job-log?id=' + id)
|
||||
} else {
|
||||
push('/job/job-log')
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="任务详细" width="700px">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="日志编号" min-width="60">
|
||||
{{ detailData.id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="任务编号">
|
||||
{{ detailData.jobId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="处理器的名字">
|
||||
{{ detailData.handlerName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="处理器的参数">
|
||||
{{ detailData.handlerParam }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="第几次执行">
|
||||
{{ detailData.executeIndex }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行时间">
|
||||
{{ formatDate(detailData.beginTime) + ' ~ ' + formatDate(detailData.endTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行时长">
|
||||
{{ detailData.duration + ' 毫秒' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="任务状态">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="detailData.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行结果">
|
||||
{{ detailData.duration + ' result' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as JobLogApi from '@/api/infra/jobLog'
|
||||
|
||||
defineOptions({ name: 'JobLogDetail' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref({} as JobLogApi.JobLogVO) // 详情数据
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
// 查看,设置数据
|
||||
if (id) {
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detailData.value = await JobLogApi.getJobLog(id)
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="处理器的名字" prop="handlerName">
|
||||
<el-input
|
||||
v-model="queryParams.handlerName"
|
||||
placeholder="请输入处理器的名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始执行时间" prop="beginTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.beginTime"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择开始执行时间"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束执行时间" prop="endTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.endTime"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择结束执行时间"
|
||||
clearable
|
||||
:default-time="new Date('1 23:59:59')"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择任务状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_JOB_LOG_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:job:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="日志编号" align="center" prop="id" />
|
||||
<el-table-column label="任务编号" align="center" prop="jobId" />
|
||||
<el-table-column label="处理器的名字" align="center" prop="handlerName" />
|
||||
<el-table-column label="处理器的参数" align="center" prop="handlerParam" />
|
||||
<el-table-column label="第几次执行" align="center" prop="executeIndex" />
|
||||
<el-table-column label="执行时间" align="center" width="170s">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.beginTime) + ' ~ ' + formatDate(scope.row.endTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行时长" align="center" prop="duration">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.duration + ' 毫秒' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="任务状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openDetail(scope.row.id)"
|
||||
v-hasPermi="['infra:job:query']"
|
||||
>
|
||||
详细
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:查看 -->
|
||||
<JobLogDetail ref="detailRef" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import JobLogDetail from './JobLogDetail.vue'
|
||||
import * as JobLogApi from '@/api/infra/jobLog'
|
||||
|
||||
defineOptions({ name: 'InfraJobLog' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { query } = useRoute() // 查询参数
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
jobId: query.id,
|
||||
handlerName: undefined,
|
||||
beginTime: undefined,
|
||||
endTime: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await JobLogApi.getJobLogPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 查看操作 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (rowId?: number) => {
|
||||
detailRef.value.open(rowId)
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await JobLogApi.exportJobLog(queryParams)
|
||||
download.excel(data, '定时任务执行日志.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
|
||||
<el-row>
|
||||
<!-- 基本信息 -->
|
||||
<el-col :span="24" class="card-box" shadow="hover">
|
||||
<el-card>
|
||||
<el-descriptions title="基本信息" :column="6" border>
|
||||
<el-descriptions-item label="Redis版本 :">
|
||||
{{ cache?.info?.redis_version }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运行模式 :">
|
||||
{{ cache?.info?.redis_mode == 'standalone' ? '单机' : '集群' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="端口 :">
|
||||
{{ cache?.info?.tcp_port }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="客户端数 :">
|
||||
{{ cache?.info?.connected_clients }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运行时间(天) :">
|
||||
{{ cache?.info?.uptime_in_days }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="使用内存 :">
|
||||
{{ cache?.info?.used_memory_human }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="使用CPU :">
|
||||
{{ cache?.info ? parseFloat(cache?.info?.used_cpu_user_children).toFixed(2) : '' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="内存配置 :">
|
||||
{{ cache?.info?.maxmemory_human }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="AOF是否开启 :">
|
||||
{{ cache?.info?.aof_enabled == '0' ? '否' : '是' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="RDB是否成功 :">
|
||||
{{ cache?.info?.rdb_last_bgsave_status }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="Key数量 :">
|
||||
{{ cache?.dbSize }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="网络入口/出口 :">
|
||||
{{ cache?.info?.instantaneous_input_kbps }}kps/
|
||||
{{ cache?.info?.instantaneous_output_kbps }}kps
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 命令统计 -->
|
||||
<el-col :span="12" class="mt-3">
|
||||
<el-card :gutter="12" shadow="hover">
|
||||
<Echart :options="commandStatsRefChika" :height="420" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 内存使用量统计 -->
|
||||
<el-col :span="12" class="mt-3">
|
||||
<el-card class="ml-3" :gutter="12" shadow="hover">
|
||||
<Echart :options="usedmemoryEchartChika" :height="420" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as RedisApi from '@/api/infra/redis'
|
||||
import { RedisMonitorInfoVO } from '@/api/infra/redis/types'
|
||||
const cache = ref<RedisMonitorInfoVO>()
|
||||
|
||||
// 基本信息
|
||||
const readRedisInfo = async () => {
|
||||
const data = await RedisApi.getCache()
|
||||
cache.value = data
|
||||
}
|
||||
|
||||
// 内存使用情况
|
||||
const usedmemoryEchartChika = reactive<any>({
|
||||
title: {
|
||||
// 仪表盘标题。
|
||||
text: '内存使用情况',
|
||||
left: 'center',
|
||||
show: true, // 是否显示标题,默认 true。
|
||||
offsetCenter: [0, '20%'], //相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
|
||||
color: 'yellow', // 文字的颜色,默认 #333。
|
||||
fontSize: 20 // 文字的字体大小,默认 15。
|
||||
},
|
||||
toolbox: {
|
||||
show: false,
|
||||
feature: {
|
||||
restore: { show: true },
|
||||
saveAsImage: { show: true }
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '峰值',
|
||||
type: 'gauge',
|
||||
min: 0,
|
||||
max: 50,
|
||||
splitNumber: 10,
|
||||
//这是指针的颜色
|
||||
color: '#F5C74E',
|
||||
radius: '85%',
|
||||
center: ['50%', '50%'],
|
||||
startAngle: 225,
|
||||
endAngle: -45,
|
||||
axisLine: {
|
||||
// 坐标轴线
|
||||
lineStyle: {
|
||||
// 属性lineStyle控制线条样式
|
||||
color: [
|
||||
[0.2, '#7FFF00'],
|
||||
[0.8, '#00FFFF'],
|
||||
[1, '#FF0000']
|
||||
],
|
||||
//width: 6 外框的大小(环的宽度)
|
||||
width: 10
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
// 坐标轴小标记
|
||||
//里面的线长是5(短线)
|
||||
length: 5, // 属性length控制线长
|
||||
lineStyle: {
|
||||
// 属性lineStyle控制线条样式
|
||||
color: '#76D9D7'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
// 分隔线
|
||||
length: 20, // 属性length控制线长
|
||||
lineStyle: {
|
||||
// 属性lineStyle(详见lineStyle)控制线条样式
|
||||
color: '#76D9D7'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#76D9D7',
|
||||
distance: 15,
|
||||
fontSize: 15
|
||||
},
|
||||
pointer: {
|
||||
// 指针的大小
|
||||
width: 7,
|
||||
show: true
|
||||
},
|
||||
detail: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
// 里面文字下的数值大小(50)
|
||||
fontSize: 15,
|
||||
color: '#FFFFFF'
|
||||
},
|
||||
valueAnimation: true
|
||||
},
|
||||
progress: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 指令使用情况
|
||||
const commandStatsRefChika = reactive({
|
||||
title: {
|
||||
text: '命令统计',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
orient: 'vertical',
|
||||
right: 30,
|
||||
top: 10,
|
||||
bottom: 20,
|
||||
data: [] as any[],
|
||||
textStyle: {
|
||||
color: '#a1a1a1'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '命令',
|
||||
type: 'pie',
|
||||
radius: [20, 120],
|
||||
center: ['40%', '60%'],
|
||||
data: [] as any[],
|
||||
roseType: 'radius',
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
/** 加载数据 */
|
||||
const getSummary = () => {
|
||||
// 初始化命令图表
|
||||
initCommandStatsChart()
|
||||
usedMemoryInstance()
|
||||
}
|
||||
|
||||
/** 命令使用情况 */
|
||||
const initCommandStatsChart = async () => {
|
||||
usedmemoryEchartChika.series[0].data = []
|
||||
// 发起请求
|
||||
try {
|
||||
const data = await RedisApi.getCache()
|
||||
cache.value = data
|
||||
// 处理数据
|
||||
const commandStats = [] as any[]
|
||||
const nameList = [] as string[]
|
||||
data.commandStats.forEach((row) => {
|
||||
commandStats.push({
|
||||
name: row.command,
|
||||
value: row.calls
|
||||
})
|
||||
nameList.push(row.command)
|
||||
})
|
||||
commandStatsRefChika.legend.data = nameList
|
||||
commandStatsRefChika.series[0].data = commandStats
|
||||
} catch {}
|
||||
}
|
||||
const usedMemoryInstance = async () => {
|
||||
try {
|
||||
const data = await RedisApi.getCache()
|
||||
cache.value = data
|
||||
// 仪表盘详情,用于显示数据。
|
||||
usedmemoryEchartChika.series[0].detail = {
|
||||
show: true, // 是否显示详情,默认 true。
|
||||
offsetCenter: [0, '50%'], // 相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
|
||||
color: 'auto', // 文字的颜色,默认 auto。
|
||||
fontSize: 30, // 文字的字体大小,默认 15。
|
||||
formatter: cache.value!.info.used_memory_human // 格式化函数或者字符串
|
||||
}
|
||||
|
||||
usedmemoryEchartChika.series[0].data[0] = {
|
||||
value: cache.value!.info.used_memory_human,
|
||||
name: '内存消耗'
|
||||
}
|
||||
console.log(cache.value!.info)
|
||||
usedmemoryEchartChika.tooltip = {
|
||||
formatter: '{b} <br/>{a} : ' + cache.value!.info.used_memory_human
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
// 读取 redis 信息
|
||||
readRedisInfo()
|
||||
// 加载数据
|
||||
getSummary()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraAdminServer' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/admin/applications')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.spring-boot-admin')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraSkyWalking' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref('http://skywalking.shop.yixiang.co')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.skywalking')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<IFrame :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraSwagger' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/doc.html') // Knife4j UI
|
||||
// const src = ref(import.meta.env.VITE_BASE_URL + '/swagger-ui') // Swagger UI
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.swagger')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<!-- 左侧:建立连接、发送消息 -->
|
||||
<el-card :gutter="12" class="w-1/2" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>连接</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-4 text-lg font-medium"> 连接状态: </span>
|
||||
<el-tag :color="getTagColor">{{ status }}</el-tag>
|
||||
</div>
|
||||
<hr class="my-4" />
|
||||
<div class="flex">
|
||||
<el-input v-model="server" disabled>
|
||||
<template #prepend>服务地址</template>
|
||||
</el-input>
|
||||
<el-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggleConnectStatus">
|
||||
{{ getIsOpen ? '关闭连接' : '开启连接' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<p class="mt-4 text-lg font-medium">消息输入框</p>
|
||||
<hr class="my-4" />
|
||||
<el-input
|
||||
v-model="sendText"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
:disabled="!getIsOpen"
|
||||
clearable
|
||||
type="textarea"
|
||||
placeholder="请输入你要发送的消息"
|
||||
/>
|
||||
<el-select v-model="sendUserId" class="mt-4" placeholder="请选择发送人">
|
||||
<el-option key="" label="所有人" value="" />
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button :disabled="!getIsOpen" block class="ml-2 mt-4" type="primary" @click="handlerSend">
|
||||
发送
|
||||
</el-button>
|
||||
</el-card>
|
||||
<!-- 右侧:消息记录 -->
|
||||
<el-card :gutter="12" class="w-1/2" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>消息记录</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="max-h-80 overflow-auto">
|
||||
<ul>
|
||||
<li v-for="msg in messageReverseList" :key="msg.time" class="mt-2">
|
||||
<div class="flex items-center">
|
||||
<span class="text-primary mr-2 font-medium">收到消息:</span>
|
||||
<span>{{ formatDate(msg.time) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ msg.text }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { useWebSocket } from '@vueuse/core'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'InfraWebSocket' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const server = ref(
|
||||
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') + '?token=' + getAccessToken()
|
||||
) // WebSocket 服务地址
|
||||
const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开
|
||||
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色
|
||||
|
||||
/** 发起 WebSocket 连接 */
|
||||
const { status, data, send, close, open } = useWebSocket(server.value, {
|
||||
autoReconnect: false,
|
||||
heartbeat: true
|
||||
})
|
||||
|
||||
/** 监听接收到的数据 */
|
||||
const messageList = ref([] as { time: number; text: string }[]) // 消息列表
|
||||
const messageReverseList = computed(() => messageList.value.slice().reverse())
|
||||
watchEffect(() => {
|
||||
if (!data.value) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 1. 收到心跳
|
||||
if (data.value === 'pong') {
|
||||
// state.recordList.push({
|
||||
// text: '【心跳】',
|
||||
// time: new Date().getTime()
|
||||
// })
|
||||
return
|
||||
}
|
||||
|
||||
// 2.1 解析 type 消息类型
|
||||
const jsonMessage = JSON.parse(data.value)
|
||||
const type = jsonMessage.type
|
||||
const content = JSON.parse(jsonMessage.content)
|
||||
if (!type) {
|
||||
message.error('未知的消息类型:' + data.value)
|
||||
return
|
||||
}
|
||||
// 2.2 消息类型:demo-message-receive
|
||||
if (type === 'demo-message-receive') {
|
||||
const single = content.single
|
||||
if (single) {
|
||||
messageList.value.push({
|
||||
text: `【单发】用户编号(${content.fromUserId}):${content.text}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
} else {
|
||||
messageList.value.push({
|
||||
text: `【群发】用户编号(${content.fromUserId}):${content.text}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
// 2.3 消息类型:notice-push
|
||||
if (type === 'notice-push') {
|
||||
messageList.value.push({
|
||||
text: `【系统通知】:${content.title}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
return
|
||||
}
|
||||
message.error('未处理消息:' + data.value)
|
||||
} catch (error) {
|
||||
message.error('处理消息发生异常:' + data.value)
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
|
||||
/** 发送消息 */
|
||||
const sendText = ref('') // 发送内容
|
||||
const sendUserId = ref('') // 发送人
|
||||
const handlerSend = () => {
|
||||
// 1.1 先 JSON 化 message 消息内容
|
||||
const messageContent = JSON.stringify({
|
||||
text: sendText.value,
|
||||
toUserId: sendUserId.value
|
||||
})
|
||||
// 1.2 再 JSON 化整个消息
|
||||
const jsonMessage = JSON.stringify({
|
||||
type: 'demo-message-send',
|
||||
content: messageContent
|
||||
})
|
||||
// 2. 最后发送消息
|
||||
send(jsonMessage)
|
||||
sendText.value = ''
|
||||
}
|
||||
|
||||
/** 切换 websocket 连接状态 */
|
||||
const toggleConnectStatus = () => {
|
||||
if (getIsOpen.value) {
|
||||
close()
|
||||
} else {
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
// 获取用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="展示店铺" prop="shopId">
|
||||
<el-select
|
||||
v-model="formData.shopId"
|
||||
placeholder="选择店铺"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in shopList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="可用类型" prop="type">
|
||||
<el-radio-group v-model="formData.type">
|
||||
<el-radio :label="0">通用</el-radio>
|
||||
<el-radio :label="1">自取</el-radio>
|
||||
<el-radio :label="2">外卖</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="兑换码" prop="exchangeCode">
|
||||
<el-input v-model="formData.exchangeCode" placeholder="请输入兑换码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图片" prop="image">
|
||||
<Materials v-model="formData.image" num="1" type="image" />
|
||||
</el-form-item>
|
||||
<el-form-item label="优惠券名称" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入优惠券名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="消费多少可用" prop="least">
|
||||
<el-input v-model="formData.least" placeholder="请输入消费多少可用" />
|
||||
</el-form-item>
|
||||
<el-form-item label="优惠券金额" prop="value">
|
||||
<el-input v-model="formData.value" placeholder="请输入优惠券金额" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
v-model="formData.startTime"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择开始时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
v-model="formData.endTime"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择结束时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="发行数量" prop="distribute">
|
||||
<el-input v-model="formData.distribute" placeholder="请输入发行数量" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所需积分" prop="score">
|
||||
<el-input v-model="formData.score" placeholder="请输入所需积分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="限领数量" prop="limit">
|
||||
<el-input v-model="formData.limit" placeholder="请输入限领数量" />
|
||||
</el-form-item>
|
||||
<el-form-item label="使用说明" prop="instructions">
|
||||
<el-input type="textarea" rows="5" v-model="formData.instructions" placeholder="请输入使用说明" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否上架" prop="isSwitch">
|
||||
<el-radio-group v-model="formData.isSwitch">
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as Api from '@/api/mall/coupon/'
|
||||
import * as ShopApi from '@/api/mall/store/shop'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
shopId: undefined,
|
||||
shopName: undefined,
|
||||
title: undefined,
|
||||
isSwitch: undefined,
|
||||
least: undefined,
|
||||
value: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
weigh: undefined,
|
||||
type: undefined,
|
||||
exchangeCode: undefined,
|
||||
receive: undefined,
|
||||
distribute: undefined,
|
||||
score: undefined,
|
||||
instructions: undefined,
|
||||
image: undefined,
|
||||
limit: undefined
|
||||
})
|
||||
const shopList = ref([])
|
||||
const formRules = reactive({
|
||||
shopId: [{ required: true, message: '店铺id,0表示通用不能为空', trigger: 'blur' }],
|
||||
title: [{ required: true, message: '优惠券名称不能为空', trigger: 'blur' }],
|
||||
least: [{ required: true, message: '消费多少可用不能为空', trigger: 'blur' }],
|
||||
value: [{ required: true, message: '优惠券金额不能为空', trigger: 'blur' }],
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '可用类型不能为空', trigger: 'blur' }],
|
||||
distribute: [{ required: true, message: '发行数量不能为空', trigger: 'blur' }],
|
||||
limit: [{ required: true, message: '限领数量不能为空', trigger: 'blur' }],
|
||||
instructions: [{ required: true, message: '使用说明不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
getList()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await Api.getCoupon(id)
|
||||
formData.value.shopId = Number(formData.value.shopId)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as Api.VO
|
||||
if (formType.value === 'create') {
|
||||
await Api.createCoupon(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await Api.updateCoupon(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
const getList = async () => {
|
||||
try {
|
||||
const data = await ShopApi.getShopList()
|
||||
shopList.value = data
|
||||
|
||||
} finally {
|
||||
|
||||
}
|
||||
}
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
shopId: undefined,
|
||||
shopName: undefined,
|
||||
title: undefined,
|
||||
isSwitch: 1,
|
||||
least: undefined,
|
||||
value: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
weigh: undefined,
|
||||
type: 0,
|
||||
exchangeCode: undefined,
|
||||
receive: undefined,
|
||||
distribute: undefined,
|
||||
score: undefined,
|
||||
instructions: undefined,
|
||||
image: undefined,
|
||||
limit: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="店铺名称" prop="shopName">
|
||||
<el-input
|
||||
v-model="queryParams.shopName"
|
||||
placeholder="请输入店铺名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="优惠券名称" prop="title">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入优惠券名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button type="primary" @click="openForm('create')" v-hasPermi="['coupon::create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<!-- <el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['coupon::export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="id" align="center" prop="id" />
|
||||
<el-table-column label="可用店铺" align="center" prop="shopName" width="150" />
|
||||
<el-table-column label="优惠券名称" align="center" prop="title" width="150" />
|
||||
<el-table-column label="是否上架" align="center" prop="isSwitch">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.isSwitch == 1">上架</span>
|
||||
<span v-else>下架</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="消费多少可用" align="center" prop="least" width="150" />
|
||||
<el-table-column label="优惠券金额" align="center" prop="value" width="150"/>
|
||||
<el-table-column label="可用类型" align="center" prop="type">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.type == 1">自取</span>
|
||||
<span v-if="scope.row.type == 0">外卖</span>
|
||||
<span v-else>通用</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="兑换码" align="center" prop="exchangeCode" />
|
||||
<el-table-column label="已领" align="center" prop="receive" />
|
||||
<el-table-column label="发行数量" align="center" prop="distribute" />
|
||||
<el-table-column label="限领数量" align="center" prop="limit" />
|
||||
<el-table-column
|
||||
label="开始时间"
|
||||
align="center"
|
||||
prop="startTime"
|
||||
:formatter="dateFormatter"
|
||||
width="170"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
:formatter="dateFormatter"
|
||||
width="170"
|
||||
/>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="170"
|
||||
/>
|
||||
|
||||
<el-table-column label="操作" align="center" fixed="right" width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['coupon::update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['coupon::delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('couponRecord', scope.row.id)"
|
||||
v-hasPermi="['coupon::delete']"
|
||||
>
|
||||
领取记录
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<Form ref="formRef" @success="getList" />
|
||||
<OrderRecord ref="formRef5" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Coupon">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as Api from '@/api/mall/coupon/'
|
||||
import Form from './Form.vue'
|
||||
import OrderRecord from './user/OrderRecord.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
shopId: null,
|
||||
shopName: null,
|
||||
title: null,
|
||||
switch: null,
|
||||
least: null,
|
||||
value: null,
|
||||
startTime: [],
|
||||
endtIme: [],
|
||||
createTime: [],
|
||||
weigh: null,
|
||||
type: null,
|
||||
exchangeCode: null,
|
||||
receive: null,
|
||||
distribute: null,
|
||||
score: null,
|
||||
instructions: null,
|
||||
image: null,
|
||||
limit: null
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await Api.getCouponPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const formRef5 = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
if (type == 'couponRecord') {
|
||||
formRef5.value.open(type, id)
|
||||
}else{
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await Api.deleteCoupon(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await Api.exportCoupon(queryParams)
|
||||
download.excel(data, '优惠券.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawer" :title="dialogTitle" size="50%">
|
||||
<el-table :data="tableData" style="width: 100%">
|
||||
<el-table-column label="id" align="center" prop="id" />
|
||||
<el-table-column label="店铺名称" align="center" prop="shopName" width="150" />
|
||||
<el-table-column label="优惠券名称" align="center" prop="title" width="150" />
|
||||
<el-table-column label="消费多少可用" align="center" prop="least" width="150" />
|
||||
<el-table-column label="优惠券金额" align="center" prop="value" width="150" />
|
||||
<el-table-column label="可用类型" align="center" prop="type">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.type == 1">自取</span>
|
||||
<span v-if="scope.row.type == 0">外卖</span>
|
||||
<span v-else>通用</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户昵称" align="center" prop="userId" />
|
||||
<el-table-column label="是否使用" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.type == 1">已使用</span>
|
||||
<span v-if="scope.row.type == 0">未使用</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="领取时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="170"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['coupon:user:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as UserApi from '@/api/mall/coupon/user'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const drawer = ref(false)
|
||||
const tableData = ref([])
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
drawer.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
console.log(id)
|
||||
getList(id)
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await UserApi.deleteUser(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
getList(id)
|
||||
} catch {}
|
||||
}
|
||||
const getList = async(id) => {
|
||||
tableData.value = await UserApi.getUserList(id)
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawer" :title="dialogTitle" size="50%">
|
||||
<div>
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<el-tab-pane label="用户信息" name="first">
|
||||
<el-descriptions title="基本信息" :column="2">
|
||||
<el-descriptions-item label="用户头像"><el-image style="width: 50px; height: 50px" :src="DetailData.avatar" /></el-descriptions-item>
|
||||
<el-descriptions-item label="用户昵称">{{ DetailData.nickname }}</el-descriptions-item>
|
||||
<el-descriptions-item label="余额">{{ DetailData.nowMoney }}</el-descriptions-item>
|
||||
<el-descriptions-item label="生日">{{ DetailData.birthday }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ DetailData.mobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号码">{{ DetailData.cardId }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-descriptions title="用户概况" :column="2">
|
||||
<el-descriptions-item label="绑定的会员卡">{{ DetailData.cardName ? DetailData.cardName : '无' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="积分">{{ DetailData.integral }}</el-descriptions-item>
|
||||
<el-descriptions-item label="佣金金额">{{ DetailData.brokeragePrice }}</el-descriptions-item>
|
||||
<el-descriptions-item label="连续签到天数">{{ DetailData.signNum }}</el-descriptions-item>
|
||||
<el-descriptions-item label="登录ip">{{ DetailData.loginIp }}</el-descriptions-item>
|
||||
<el-descriptions-item label="等级">{{ DetailData.level }}</el-descriptions-item>
|
||||
<el-descriptions-item label="推广id">{{ DetailData.spreadUid }}</el-descriptions-item>
|
||||
<el-descriptions-item label="购买次数">{{ DetailData.payCount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="下级人数">{{ DetailData.spreadCount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="登陆类型">{{ DetailData.loginType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="注册时间">{{ formatDate(DetailData.createTime) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="消费记录" name="second">
|
||||
<el-table :data="list">
|
||||
<el-table-column label="支出/获得" align="center" prop="pm">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.pm == 0">支出</span>
|
||||
<span v-else-if="scope.row.category == 1">获得</span>
|
||||
<span v-else>未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="账单标题" align="center" prop="title" />
|
||||
<el-table-column label="明细种类" align="center" prop="category">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.category == 'now_money'">余额</span>
|
||||
<span v-else-if="scope.row.category == 'integral'">积分</span>
|
||||
<span v-else>未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="明细类型" align="center" prop="type">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.type == 'recharge'">充值</span>
|
||||
<span v-else-if="scope.row.type == 'brokerage'">返佣</span>
|
||||
<span v-else-if="scope.row.type == 'pay_product'">消费</span>
|
||||
<span v-else-if="scope.row.type == 'extract'">提现</span>
|
||||
<span v-else-if="scope.row.type == 'pay_product_refund'">退款</span>
|
||||
<span v-else-if="scope.row.type == 'system_add'">系统添加</span>
|
||||
<span v-else-if="scope.row.type == 'system_sub'">系统减少</span>
|
||||
<span v-else-if="scope.row.type == 'deduction'">减去</span>
|
||||
<span v-else-if="scope.row.type == 'gain'">奖励</span>
|
||||
<span v-else-if="scope.row.type == 'sign'">签到</span>
|
||||
<span v-else-if="scope.row.type == 'vip_card'">会员卡</span>
|
||||
<span v-else>未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="明细数字(元)" align="center" prop="number">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.pm == 1">+</span>
|
||||
<span v-else>-</span>
|
||||
<span>{{ scope.row.number }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余(元)" align="center" prop="balance" />
|
||||
<el-table-column label="备注" align="center" prop="mark" width="200" />
|
||||
<el-table-column
|
||||
label="添加时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="100"
|
||||
/>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as UserApi from '@/api/member/user'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
import * as UserBillApi from '@/api/member/userBill'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
// const message = useMessage() // 消息弹窗
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const drawer = ref(false)
|
||||
const DetailData = ref({})
|
||||
const activeName = ref('first')
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
uid: null,
|
||||
linkId: null,
|
||||
pm: null,
|
||||
title: null,
|
||||
category: null,
|
||||
type: null,
|
||||
number: null,
|
||||
balance: null,
|
||||
mark: null,
|
||||
createTime: [],
|
||||
status: null
|
||||
})
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
drawer.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
DetailData.value = await UserApi.getUser(id)
|
||||
queryParams.uid = id
|
||||
getList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab.paneName, event)
|
||||
// queryParams.pageNo = 1
|
||||
// queryParams.type = tab.paneName
|
||||
//getList()
|
||||
}
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
try {
|
||||
const data = await UserBillApi.getUserBillPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="用户账户" prop="username">
|
||||
<el-input v-model="formData.username" placeholder="请输入用户账户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户昵称" prop="nickname">
|
||||
<el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="mobile">
|
||||
<el-input v-model="formData.mobile" placeholder="请输入手机号码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="真实姓名" prop="realName">
|
||||
<el-input v-model="formData.realName" placeholder="请输入真实姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="生日" prop="birthday">
|
||||
<el-input v-model="formData.birthday" placeholder="请输入生日" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户备注" prop="mark">
|
||||
<el-input v-model="formData.mark" placeholder="请输入用户备注" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户余额" prop="nowMoney">
|
||||
<el-input v-model="formData.nowMoney" placeholder="请输入用户余额" />
|
||||
</el-form-item>
|
||||
<el-form-item label="佣金金额" prop="brokeragePrice">
|
||||
<el-input v-model="formData.brokeragePrice" placeholder="请输入佣金金额" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户积分" prop="integral">
|
||||
<el-input v-model="formData.integral" placeholder="请输入用户剩余积分" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="等级" prop="level">
|
||||
<el-input v-model="formData.level" placeholder="请输入等级" />
|
||||
</el-form-item> -->
|
||||
<!-- <el-form-item label="是否为推广员" prop="isPromoter">
|
||||
<el-input v-model="formData.isPromoter" placeholder="请输入是否为推广员" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="详细地址" prop="addres">
|
||||
<el-input v-model="formData.addres" placeholder="请输入详细地址" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as UserApi from '@/api/member/user'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
realName: undefined,
|
||||
birthday: undefined,
|
||||
cardId: undefined,
|
||||
mark: undefined,
|
||||
partnerId: undefined,
|
||||
groupId: undefined,
|
||||
nickname: undefined,
|
||||
avatar: undefined,
|
||||
mobile: undefined,
|
||||
addIp: undefined,
|
||||
lastIp: undefined,
|
||||
nowMoney: undefined,
|
||||
brokeragePrice: undefined,
|
||||
integral: undefined,
|
||||
signNum: undefined,
|
||||
status: undefined,
|
||||
level: undefined,
|
||||
spreadUid: undefined,
|
||||
spreadTime: undefined,
|
||||
userType: undefined,
|
||||
isPromoter: undefined,
|
||||
payCount: undefined,
|
||||
spreadCount: undefined,
|
||||
addres: undefined,
|
||||
adminid: undefined,
|
||||
loginType: undefined,
|
||||
wxProfile: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
username: [{ required: true, message: '用户账户不能为空', trigger: 'blur' }],
|
||||
nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
|
||||
mobile: [{ required: true, message: '佣手机号码不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await UserApi.getUser(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as UserApi.UserVO
|
||||
if (formType.value === 'create') {
|
||||
await UserApi.createUser(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await UserApi.updateUser(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
realName: undefined,
|
||||
birthday: undefined,
|
||||
cardId: undefined,
|
||||
mark: undefined,
|
||||
partnerId: undefined,
|
||||
groupId: undefined,
|
||||
nickname: undefined,
|
||||
avatar: undefined,
|
||||
mobile: undefined,
|
||||
addIp: undefined,
|
||||
lastIp: undefined,
|
||||
nowMoney: undefined,
|
||||
brokeragePrice: undefined,
|
||||
integral: undefined,
|
||||
signNum: undefined,
|
||||
status: undefined,
|
||||
level: undefined,
|
||||
spreadUid: undefined,
|
||||
spreadTime: undefined,
|
||||
userType: undefined,
|
||||
isPromoter: undefined,
|
||||
payCount: undefined,
|
||||
spreadCount: undefined,
|
||||
addres: undefined,
|
||||
adminid: undefined,
|
||||
loginType: undefined,
|
||||
wxProfile: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="全部" name=""/>
|
||||
<el-tab-pane label="微信小程序" name="routine"/>
|
||||
<el-tab-pane label="公众号" name="wechat"/>
|
||||
<el-tab-pane label="H5" name="h5"/>
|
||||
</el-tabs>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="用户昵称" prop="nickname">
|
||||
<el-input
|
||||
v-model="queryParams.nickname"
|
||||
placeholder="请输入用户昵称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phone">
|
||||
<el-input
|
||||
v-model="queryParams.phone"
|
||||
placeholder="请输入手机号码"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="添加时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button type="primary" @click="openForm('create')" v-hasPermi="['member:user:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<!-- <el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['member:user:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="id" align="center" prop="id" />
|
||||
<el-table-column label="用户昵称" align="center" prop="nickname" />
|
||||
<el-table-column label="用户头像" align="center" prop="avatar">
|
||||
<template #default="scope">
|
||||
<el-image style="width: 50px; height: 50px" :src="scope.row.avatar" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手机号码" align="center" prop="mobile" />
|
||||
<el-table-column
|
||||
label="添加时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="用户余额" align="center" prop="nowMoney" />
|
||||
<el-table-column label="用户登陆类型" align="center" prop="loginType" />
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<div class="flex justify-center items-center">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('userDetail', scope.row.id)"
|
||||
v-hasPermi="['member:user:update']"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-dropdown>
|
||||
<el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openForm('update', scope.row.id)">编辑</el-dropdown-item>
|
||||
<el-dropdown-item @click="openForm('yue', scope.row.id)">积分余额</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<UserForm ref="formRef" @success="getList" />
|
||||
<UserDetail ref="formRef1" />
|
||||
<Yue ref="formRef2" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="User">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as UserApi from '@/api/member/user'
|
||||
import UserForm from './UserForm.vue'
|
||||
import UserDetail from './UserDetail.vue'
|
||||
import Yue from './yue.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
// const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
username: null,
|
||||
realName: null,
|
||||
nickname: null,
|
||||
phone: null,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const activeName = ref('')
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await UserApi.getUserPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab, event)
|
||||
queryParams.pageNo = 1
|
||||
queryParams.loginType = tab.paneName
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const formRef1 = ref()
|
||||
const formRef2 = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
if (type == 'update' || type == 'create') {
|
||||
formRef.value.open(type, id)
|
||||
} else if (type == 'userDetail') {
|
||||
formRef1.value.open(type, id)
|
||||
} else if (type == 'yue') {
|
||||
formRef2.value.open(type, id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await UserApi.exportUser(queryParams)
|
||||
download.excel(data, '用户.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="修改余额">
|
||||
<el-radio-group v-model="formData.ptype">
|
||||
<el-radio :label="1">增加</el-radio>
|
||||
<el-radio :label="2">减少</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="余额">
|
||||
<el-input v-model="formData.money" style="width: 370px;" placeholder="请输入余额" />
|
||||
</el-form-item>
|
||||
<el-form-item label="修改积分">
|
||||
<el-radio-group v-model="formData.itype">
|
||||
<el-radio :label="1">增加</el-radio>
|
||||
<el-radio :label="2">减少</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="积分">
|
||||
<el-input v-model="formData.integral" style="width: 370px;" placeholder="请输入积分" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as UserApi from '@/api/member/user'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
ptype: 1,
|
||||
money: undefined,
|
||||
itype: 1,
|
||||
integral: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
money: [{ required: true, message: '金额不能为空', trigger: 'blur' }],
|
||||
integral: [{ required: true, message: '积分不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// formLoading.value = true
|
||||
formData.value.id = id
|
||||
console.log(type,id)
|
||||
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as UserApi.UserVO
|
||||
await UserApi.updateMony(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
ptype: 1,
|
||||
money: undefined,
|
||||
itype: 1,
|
||||
integral: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="150px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="收货人姓名" prop="realName">
|
||||
<el-input v-model="formData.realName" placeholder="请输入收货人姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="收货人电话" prop="phone">
|
||||
<el-input v-model="formData.phone" placeholder="请输入收货人电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="收货人地址" prop="address">
|
||||
<el-input v-model="formData.address" placeholder="请输入收货人所在省" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="收货人所在市" prop="city">
|
||||
<el-input v-model="formData.city" placeholder="请输入收货人所在市" />
|
||||
</el-form-item>
|
||||
<el-form-item label="收货人所在区" prop="district">
|
||||
<el-input v-model="formData.district" placeholder="请输入收货人所在区" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="收货人详细地址" prop="detail">
|
||||
<el-input v-model="formData.detail" placeholder="请输入收货人详细地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否默认" prop="isDefault">
|
||||
<el-radio-group v-model="formData.isDefault">
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as UserAddressApi from '@/api/member/userAddress'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
phone: undefined,
|
||||
province: undefined,
|
||||
city: undefined,
|
||||
cityId: undefined,
|
||||
district: undefined,
|
||||
detail: undefined,
|
||||
postCode: undefined,
|
||||
longitude: undefined,
|
||||
latitude: undefined,
|
||||
isDefault: undefined,
|
||||
address: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
realName: [{ required: true, message: '收货人姓名不能为空', trigger: 'blur' }],
|
||||
phone: [{ required: true, message: '收货人电话不能为空', trigger: 'blur' }],
|
||||
address: [{ required: true, message: '收货人地址不能为空', trigger: 'blur' }],
|
||||
detail: [{ required: true, message: '收货人详细地址不能为空', trigger: 'blur' }],
|
||||
isDefault: [{ required: true, message: '是否默认不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await UserAddressApi.getUserAddress(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as UserAddressApi.UserAddressVO
|
||||
if (formType.value === 'create') {
|
||||
await UserAddressApi.createUserAddress(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await UserAddressApi.updateUserAddress(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
phone: undefined,
|
||||
province: undefined,
|
||||
city: undefined,
|
||||
cityId: undefined,
|
||||
district: undefined,
|
||||
detail: undefined,
|
||||
postCode: undefined,
|
||||
longitude: undefined,
|
||||
latitude: undefined,
|
||||
isDefault: undefined,
|
||||
address: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="queryParams.realName"
|
||||
placeholder="请输入收货人姓名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="电话" prop="phone">
|
||||
<el-input
|
||||
v-model="queryParams.phone"
|
||||
placeholder="请输入收货人电话"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="id" align="center" prop="id" />
|
||||
<el-table-column label="收货人姓名" align="center" prop="realName" />
|
||||
<el-table-column label="收货人电话" align="center" prop="phone" />
|
||||
<el-table-column label="收货人地址" align="center" prop="address" />
|
||||
<el-table-column label="收货人详细地址" align="center" prop="detail" />
|
||||
<el-table-column label="是否默认" align="center" prop="isDefault">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.isDefault == 1">是</span>
|
||||
<span v-else>否</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="添加时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="150">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['member:user-address:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['member:user-address:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<UserAddressForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="UserAddress">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as UserAddressApi from '@/api/member/userAddress'
|
||||
import UserAddressForm from './UserAddressForm.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
uid: null,
|
||||
realName: null,
|
||||
phone: null,
|
||||
province: null,
|
||||
city: null,
|
||||
cityId: null,
|
||||
district: null,
|
||||
detail: null,
|
||||
postCode: null,
|
||||
longitude: null,
|
||||
latitude: null,
|
||||
isDefault: null,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await UserAddressApi.getUserAddressPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await UserAddressApi.deleteUserAddress(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawer" :title="dialogTitle" size="40%">
|
||||
<div>
|
||||
<el-descriptions title="收货信息1" :column="2" v-if="DetailData.orderType == 'takeout'">
|
||||
<el-descriptions-item label="用户昵称">{{ nickname }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收货人">{{ DetailData.realName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ DetailData.userPhone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收货地址">{{ DetailData.userAddress }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-descriptions title="商品明细" :column="1">
|
||||
<el-descriptions-item label="菜品">
|
||||
<div v-for="(num,k) in addProductMark + 1" :key="k">
|
||||
<div v-if="num > 1" style="text-align:center;font-weight:bold;font-size:22px">
|
||||
第{{ num - 1 }}次加菜
|
||||
</div>
|
||||
<div v-else style="text-align:center;font-weight:bold;font-size:22px">
|
||||
首次点菜
|
||||
</div>
|
||||
<table width="100%">
|
||||
<tr style="font-weight:bold;height:50px">
|
||||
<td>图片</td>
|
||||
<td>名称</td>
|
||||
<td>价格</td>
|
||||
<td>数量</td>
|
||||
<td>状态</td>
|
||||
</tr>
|
||||
<tr v-for="(val, i ) in product" :key="i" >
|
||||
<td v-if="val.addProductMark == num-1 "><el-image style="width: 40px; height: 40px" :src="val.image" :fit="fit" /></td>
|
||||
<td v-if="val.addProductMark == num-1">{{ val.title}}</td>
|
||||
<td v-if="val.addProductMark == num-1">{{ '¥'+ val.price}}</td>
|
||||
<td v-if="val.addProductMark == num-1">{{ ' x '+ val.number}}</td>
|
||||
<td v-if="val.addProductMark == num-1">
|
||||
<el-tag type="success" v-if="val.isOrder==1">已出单</el-tag>
|
||||
<el-tag type="danger" v-else>未出单</el-tag>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-descriptions title="订单信息" :column="2">
|
||||
<template #title>
|
||||
订单信息
|
||||
<el-tag type="danger" v-if="DetailData.orderType=='desk'">堂食</el-tag>
|
||||
<el-tag type="danger" v-if="DetailData.orderType=='takeout'">外卖</el-tag>
|
||||
<el-tag type="danger" v-if="DetailData.orderType=='takein'">自取</el-tag>
|
||||
</template>
|
||||
<el-descriptions-item label="门店">{{ DetailData.shopName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="取餐号">{{ DetailData.numberId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="桌位号">{{ DetailData.deskNumber ? DetailData.deskNumber : '无' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="就餐人数">{{ DetailData.deskPeople ? DetailData.deskPeople : '无' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="订单号">{{ DetailData.orderId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="订单状态">{{ DetailData.statusStr }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商品总数">{{ DetailData.totalNum }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商品总价">{{ DetailData.totalPrice }}</el-descriptions-item>
|
||||
<el-descriptions-item label="支付邮费">{{ DetailData.payPostage }}</el-descriptions-item>
|
||||
<el-descriptions-item label="优惠券金额">{{ DetailData.couponPrice }}</el-descriptions-item>
|
||||
<el-descriptions-item label="积分抵扣">{{ DetailData.useIntegral }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实际支付">{{ DetailData.payPrice }}</el-descriptions-item>
|
||||
<el-descriptions-item label="赠送积分">{{ DetailData.gainIntegral }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ formatDate(DetailData.createTime)}}</el-descriptions-item>
|
||||
<el-descriptions-item label="支付时间">{{ formatDate(DetailData.payTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="支付方式">{{ DetailData.payType }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<!-- <el-descriptions title="物流信息" :column="2">
|
||||
<el-descriptions-item label="快递公司">{{ DetailData.deliveryName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="快递单号">{{ DetailData.deliveryId }} <el-button type="primary" @click="getLogistic(DetailData.deliverySn,DetailData.deliveryId)">物流追踪</el-button></el-descriptions-item>
|
||||
</el-descriptions> -->
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(activity, index) in logisticResult"
|
||||
:key="index"
|
||||
:timestamp="activity.acceptTime"
|
||||
>
|
||||
{{ activity.acceptStation }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<!-- <el-button @click="innerDrawer = true">Click me!</el-button>
|
||||
<el-drawer
|
||||
v-model="innerDrawer"
|
||||
title="I'm inner Drawer"
|
||||
:append-to-body="true"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<p>_(:зゝ∠)_</p>
|
||||
</el-drawer> -->
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as StoreOrderApi from '@/api/mall/order/storeOrder'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
//const message = useMessage() // 消息弹窗
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
// const message = useMessage() // 消息弹窗
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const drawer = ref(false)
|
||||
const DetailData = ref({})
|
||||
const nickname = ref('')
|
||||
const logisticResult = ref({})
|
||||
const product = ref([])
|
||||
const addProductMark = ref(0)
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
drawer.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
DetailData.value = await StoreOrderApi.getStoreOrder(id)
|
||||
nickname.value = DetailData.value.userRespVO.nickname
|
||||
product.value = DetailData.value.storeOrderCartInfoDOList
|
||||
//获取加餐数量
|
||||
addProductMark.value = DetailData.value.storeOrderCartInfoDOList[DetailData.value.storeOrderCartInfoDOList.length - 1].addProductMark
|
||||
|
||||
console.log('addProductMark.value:',addProductMark.value )
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
//const getLogistic = async(deliverySn,deliveryId) => {
|
||||
// const res = await StoreOrderApi.getLogistic(deliverySn, deliveryId)
|
||||
// if (res.success == "false") {
|
||||
// message.error(res.reason)
|
||||
// }
|
||||
// logisticResult.value = res.traces
|
||||
// }
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawer" :title="dialogTitle" size="40%">
|
||||
<el-table :data="tableData" style="width: 100%">
|
||||
<el-table-column prop="oid" label="订单id" width="180" />
|
||||
<el-table-column prop="changeMessage" label="操作记录" width="180" />
|
||||
<el-table-column prop="changeTime" :formatter="dateFormatter" label="操作时间" />
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as StoreOrderApi from '@/api/mall/order/storeOrder'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
// const message = useMessage() // 消息弹窗
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const drawer = ref(false)
|
||||
const tableData = ref([])
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
drawer.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
console.log(id)
|
||||
tableData.value = await StoreOrderApi.getStoreOrderRecordList(id)
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="订单号" prop="orderType">
|
||||
<el-input v-model="formData.orderId" disabled class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item label="桌号" prop="orderType" v-if="formData.orderType == 'desk'">
|
||||
<el-input v-model="formData.deskNumber" disabled class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item label="取餐号" prop="orderType" v-if="formData.orderType == 'takein'">
|
||||
<el-input v-model="formData.numberId" disabled class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" prop="orderType" v-if="formData.orderType == 'takeout'">
|
||||
<span>{{ formData.realName }}{{ formData.userPhone }}{{ formData.userAddress }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">打 印</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as StoreOrderApi from '@/api/mall/order/storeOrder'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
updateType: "",
|
||||
orderId: undefined,
|
||||
orderType: undefined,
|
||||
extendOrderId: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
userPhone: undefined,
|
||||
userAddress: undefined,
|
||||
cartId: undefined,
|
||||
freightPrice: undefined,
|
||||
totalNum: undefined,
|
||||
totalPrice: undefined,
|
||||
totalPostage: undefined,
|
||||
payPrice: undefined,
|
||||
payPostage: undefined,
|
||||
deductionPrice: undefined,
|
||||
couponId: undefined,
|
||||
couponPrice: undefined,
|
||||
paid: undefined,
|
||||
payTime: undefined,
|
||||
payType: undefined,
|
||||
status: undefined,
|
||||
refundStatus: undefined,
|
||||
refundReasonWapImg: undefined,
|
||||
refundReasonWapExplain: undefined,
|
||||
refundReasonTime: undefined,
|
||||
refundReasonWap: undefined,
|
||||
refundReason: undefined,
|
||||
refundPrice: undefined,
|
||||
deliverySn: undefined,
|
||||
deliveryName: undefined,
|
||||
deliveryType: 'normal',
|
||||
deliveryId: undefined,
|
||||
gainIntegral: undefined,
|
||||
useIntegral: undefined,
|
||||
payIntegral: undefined,
|
||||
backIntegral: undefined,
|
||||
mark: undefined,
|
||||
unique: undefined,
|
||||
remark: undefined,
|
||||
merId: undefined,
|
||||
combinationId: undefined,
|
||||
pinkId: undefined,
|
||||
cost: undefined,
|
||||
seckillId: undefined,
|
||||
bargainId: undefined,
|
||||
verifyCode: undefined,
|
||||
storeId: undefined,
|
||||
shippingType: undefined,
|
||||
isChannel: undefined,
|
||||
isSystemDel: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
//deliveryId: [{ required: true, message: '快递单号不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = '小票打印'
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await StoreOrderApi.getStoreOrder(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as StoreOrderApi.StoreOrderVO
|
||||
data.updateType = 'orderSend'
|
||||
await StoreOrderApi.updateStoreOrder(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
updateType: "",
|
||||
orderId: undefined,
|
||||
orderType: undefined,
|
||||
extendOrderId: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
userPhone: undefined,
|
||||
userAddress: undefined,
|
||||
cartId: undefined,
|
||||
freightPrice: undefined,
|
||||
totalNum: undefined,
|
||||
totalPrice: undefined,
|
||||
totalPostage: undefined,
|
||||
payPrice: undefined,
|
||||
payPostage: undefined,
|
||||
deductionPrice: undefined,
|
||||
couponId: undefined,
|
||||
couponPrice: undefined,
|
||||
paid: undefined,
|
||||
payTime: undefined,
|
||||
payType: undefined,
|
||||
status: undefined,
|
||||
refundStatus: undefined,
|
||||
refundReasonWapImg: undefined,
|
||||
refundReasonWapExplain: undefined,
|
||||
refundReasonTime: undefined,
|
||||
refundReasonWap: undefined,
|
||||
refundReason: undefined,
|
||||
refundPrice: undefined,
|
||||
deliverySn: undefined,
|
||||
deliveryName: undefined,
|
||||
deliveryType: 'normal',
|
||||
deliveryId: undefined,
|
||||
gainIntegral: undefined,
|
||||
useIntegral: undefined,
|
||||
payIntegral: undefined,
|
||||
backIntegral: undefined,
|
||||
mark: undefined,
|
||||
unique: undefined,
|
||||
remark: undefined,
|
||||
merId: undefined,
|
||||
combinationId: undefined,
|
||||
pinkId: undefined,
|
||||
cost: undefined,
|
||||
seckillId: undefined,
|
||||
bargainId: undefined,
|
||||
verifyCode: undefined,
|
||||
storeId: undefined,
|
||||
shippingType: undefined,
|
||||
isChannel: undefined,
|
||||
isSystemDel: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.input-width {
|
||||
width: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="订单号" prop="orderId">
|
||||
<el-input v-model="formData.orderId" disabled class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item label="快递公司" prop="deliverySn">
|
||||
<el-select v-model="formData.deliverySn" placeholder="选择快递公司" @change="selectExpress" >
|
||||
<el-option label="选择快递公司" value="" />
|
||||
<el-option
|
||||
v-for="item in express"
|
||||
:key="item.code"
|
||||
:label="item.name"
|
||||
:value="item.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="快递单号" prop="deliveryId">
|
||||
<el-input v-model="formData.deliveryId" placeholder="请输入快递单号" class="input-width" />
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as StoreOrderApi from '@/api/mall/order/storeOrder'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
updateType: "",
|
||||
orderId: undefined,
|
||||
orderType: 'send',
|
||||
extendOrderId: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
userPhone: undefined,
|
||||
userAddress: undefined,
|
||||
cartId: undefined,
|
||||
freightPrice: undefined,
|
||||
totalNum: undefined,
|
||||
totalPrice: undefined,
|
||||
totalPostage: undefined,
|
||||
payPrice: undefined,
|
||||
payPostage: undefined,
|
||||
deductionPrice: undefined,
|
||||
couponId: undefined,
|
||||
couponPrice: undefined,
|
||||
paid: undefined,
|
||||
payTime: undefined,
|
||||
payType: undefined,
|
||||
status: undefined,
|
||||
refundStatus: undefined,
|
||||
refundReasonWapImg: undefined,
|
||||
refundReasonWapExplain: undefined,
|
||||
refundReasonTime: undefined,
|
||||
refundReasonWap: undefined,
|
||||
refundReason: undefined,
|
||||
refundPrice: undefined,
|
||||
deliverySn: undefined,
|
||||
deliveryName: undefined,
|
||||
deliveryType: 'normal',
|
||||
deliveryId: undefined,
|
||||
gainIntegral: undefined,
|
||||
useIntegral: undefined,
|
||||
payIntegral: undefined,
|
||||
backIntegral: undefined,
|
||||
mark: undefined,
|
||||
unique: undefined,
|
||||
remark: undefined,
|
||||
merId: undefined,
|
||||
combinationId: undefined,
|
||||
pinkId: undefined,
|
||||
cost: undefined,
|
||||
seckillId: undefined,
|
||||
bargainId: undefined,
|
||||
verifyCode: undefined,
|
||||
storeId: undefined,
|
||||
shippingType: undefined,
|
||||
isChannel: undefined,
|
||||
isSystemDel: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
deliveryName: [{ required: true, message: '快递公司不能为空', trigger: 'blur' }],
|
||||
deliveryId: [{ required: true, message: '快递单号不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
const express = ref([])
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
//express.value = await ExpressApi.getExpressList()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await StoreOrderApi.getStoreOrder(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as StoreOrderApi.StoreOrderVO
|
||||
data.updateType = formType.value
|
||||
await StoreOrderApi.updateStoreOrder(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const selectExpress = (val) => {
|
||||
let obj = {};
|
||||
obj = express.value.find((item)=>{ // 这里的userList就是上面遍历的数据源
|
||||
return item.code === val; // 筛选出匹配数据
|
||||
})
|
||||
formData.value.deliveryName = obj.name
|
||||
}
|
||||
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
updateType: "",
|
||||
orderId: undefined,
|
||||
orderType: 'send',
|
||||
extendOrderId: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
userPhone: undefined,
|
||||
userAddress: undefined,
|
||||
cartId: undefined,
|
||||
freightPrice: undefined,
|
||||
totalNum: undefined,
|
||||
totalPrice: undefined,
|
||||
totalPostage: undefined,
|
||||
payPrice: undefined,
|
||||
payPostage: undefined,
|
||||
deductionPrice: undefined,
|
||||
couponId: undefined,
|
||||
couponPrice: undefined,
|
||||
paid: undefined,
|
||||
payTime: undefined,
|
||||
payType: undefined,
|
||||
status: undefined,
|
||||
refundStatus: undefined,
|
||||
refundReasonWapImg: undefined,
|
||||
refundReasonWapExplain: undefined,
|
||||
refundReasonTime: undefined,
|
||||
refundReasonWap: undefined,
|
||||
refundReason: undefined,
|
||||
refundPrice: undefined,
|
||||
deliverySn: undefined,
|
||||
deliveryName: undefined,
|
||||
deliveryType: 'normal',
|
||||
deliveryId: undefined,
|
||||
gainIntegral: undefined,
|
||||
useIntegral: undefined,
|
||||
payIntegral: undefined,
|
||||
backIntegral: undefined,
|
||||
mark: undefined,
|
||||
unique: undefined,
|
||||
remark: undefined,
|
||||
merId: undefined,
|
||||
combinationId: undefined,
|
||||
pinkId: undefined,
|
||||
cost: undefined,
|
||||
seckillId: undefined,
|
||||
bargainId: undefined,
|
||||
verifyCode: undefined,
|
||||
storeId: undefined,
|
||||
shippingType: undefined,
|
||||
isChannel: undefined,
|
||||
isSystemDel: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.input-width {
|
||||
width: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="订单号" prop="orderId">
|
||||
<el-input disabled v-model="formData.orderId" placeholder="请输入订单号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="订单总价" prop="totalPrice">
|
||||
<el-input disabled v-model="formData.totalPrice" placeholder="请输入订单总价" />
|
||||
</el-form-item>
|
||||
<el-form-item label="实际支付金额" prop="payPrice">
|
||||
<el-input v-model="formData.payPrice" placeholder="请输入实际支付金额" />
|
||||
</el-form-item>
|
||||
<el-form-item label="赠送积分" prop="gainIntegral">
|
||||
<el-input v-model="formData.gainIntegral" placeholder="请输入赠送积分" />
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as StoreOrderApi from '@/api/mall/order/storeOrder'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
orderId: undefined,
|
||||
extendOrderId: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
userPhone: undefined,
|
||||
userAddress: undefined,
|
||||
cartId: undefined,
|
||||
freightPrice: undefined,
|
||||
totalNum: undefined,
|
||||
totalPrice: undefined,
|
||||
totalPostage: undefined,
|
||||
payPrice: undefined,
|
||||
payPostage: undefined,
|
||||
deductionPrice: undefined,
|
||||
couponId: undefined,
|
||||
couponPrice: undefined,
|
||||
paid: undefined,
|
||||
payTime: undefined,
|
||||
payType: undefined,
|
||||
status: undefined,
|
||||
refundStatus: undefined,
|
||||
refundReasonWapImg: undefined,
|
||||
refundReasonWapExplain: undefined,
|
||||
refundReasonTime: undefined,
|
||||
refundReasonWap: undefined,
|
||||
refundReason: undefined,
|
||||
refundPrice: undefined,
|
||||
deliverySn: undefined,
|
||||
deliveryName: undefined,
|
||||
deliveryType: undefined,
|
||||
deliveryId: undefined,
|
||||
gainIntegral: undefined,
|
||||
useIntegral: undefined,
|
||||
payIntegral: undefined,
|
||||
backIntegral: undefined,
|
||||
mark: undefined,
|
||||
unique: undefined,
|
||||
remark: undefined,
|
||||
merId: undefined,
|
||||
combinationId: undefined,
|
||||
pinkId: undefined,
|
||||
cost: undefined,
|
||||
seckillId: undefined,
|
||||
bargainId: undefined,
|
||||
verifyCode: undefined,
|
||||
storeId: undefined,
|
||||
shippingType: undefined,
|
||||
isChannel: undefined,
|
||||
isSystemDel: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
totalPrice: [{ required: true, message: '订单总价不能为空', trigger: 'blur' }],
|
||||
payPrice: [{ required: true, message: '实际支付金额不能为空', trigger: 'blur' }],
|
||||
gainIntegral: [{ required: true, message: '消费赚取积分不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await StoreOrderApi.getStoreOrder(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as StoreOrderApi.StoreOrderVO
|
||||
await StoreOrderApi.updateStoreOrder(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
orderId: undefined,
|
||||
extendOrderId: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
userPhone: undefined,
|
||||
userAddress: undefined,
|
||||
cartId: undefined,
|
||||
freightPrice: undefined,
|
||||
totalNum: undefined,
|
||||
totalPrice: undefined,
|
||||
totalPostage: undefined,
|
||||
payPrice: undefined,
|
||||
payPostage: undefined,
|
||||
deductionPrice: undefined,
|
||||
couponId: undefined,
|
||||
couponPrice: undefined,
|
||||
paid: undefined,
|
||||
payTime: undefined,
|
||||
payType: undefined,
|
||||
status: undefined,
|
||||
refundStatus: undefined,
|
||||
refundReasonWapImg: undefined,
|
||||
refundReasonWapExplain: undefined,
|
||||
refundReasonTime: undefined,
|
||||
refundReasonWap: undefined,
|
||||
refundReason: undefined,
|
||||
refundPrice: undefined,
|
||||
deliverySn: undefined,
|
||||
deliveryName: undefined,
|
||||
deliveryType: undefined,
|
||||
deliveryId: undefined,
|
||||
gainIntegral: undefined,
|
||||
useIntegral: undefined,
|
||||
payIntegral: undefined,
|
||||
backIntegral: undefined,
|
||||
mark: undefined,
|
||||
unique: undefined,
|
||||
remark: undefined,
|
||||
merId: undefined,
|
||||
combinationId: undefined,
|
||||
pinkId: undefined,
|
||||
cost: undefined,
|
||||
seckillId: undefined,
|
||||
bargainId: undefined,
|
||||
verifyCode: undefined,
|
||||
storeId: undefined,
|
||||
shippingType: undefined,
|
||||
isChannel: undefined,
|
||||
isSystemDel: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="订单号" prop="orderId">
|
||||
<el-input disabled v-model="formData.orderId" placeholder="请输入订单号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="订单总价" prop="totalPrice">
|
||||
<el-input disabled v-model="formData.totalPrice" placeholder="请输入订单总价" />
|
||||
</el-form-item>
|
||||
<el-form-item label="退款金额" prop="payPrice">
|
||||
<el-input v-model="formData.payPrice" placeholder="退款金额" />
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as StoreOrderApi from '@/api/mall/order/storeOrder'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
orderId: undefined,
|
||||
extendOrderId: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
userPhone: undefined,
|
||||
userAddress: undefined,
|
||||
cartId: undefined,
|
||||
freightPrice: undefined,
|
||||
totalNum: undefined,
|
||||
totalPrice: undefined,
|
||||
totalPostage: undefined,
|
||||
payPrice: undefined,
|
||||
payPostage: undefined,
|
||||
deductionPrice: undefined,
|
||||
couponId: undefined,
|
||||
couponPrice: undefined,
|
||||
paid: undefined,
|
||||
payTime: undefined,
|
||||
payType: undefined,
|
||||
status: undefined,
|
||||
refundStatus: undefined,
|
||||
refundReasonWapImg: undefined,
|
||||
refundReasonWapExplain: undefined,
|
||||
refundReasonTime: undefined,
|
||||
refundReasonWap: undefined,
|
||||
refundReason: undefined,
|
||||
refundPrice: undefined,
|
||||
deliverySn: undefined,
|
||||
deliveryName: undefined,
|
||||
deliveryType: undefined,
|
||||
deliveryId: undefined,
|
||||
gainIntegral: undefined,
|
||||
useIntegral: undefined,
|
||||
payIntegral: undefined,
|
||||
backIntegral: undefined,
|
||||
mark: undefined,
|
||||
unique: undefined,
|
||||
remark: undefined,
|
||||
merId: undefined,
|
||||
combinationId: undefined,
|
||||
pinkId: undefined,
|
||||
cost: undefined,
|
||||
seckillId: undefined,
|
||||
bargainId: undefined,
|
||||
verifyCode: undefined,
|
||||
storeId: undefined,
|
||||
shippingType: undefined,
|
||||
isChannel: undefined,
|
||||
isSystemDel: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
totalPrice: [{ required: true, message: '订单总价不能为空', trigger: 'blur' }],
|
||||
payPrice: [{ required: true, message: '退款金额金额不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await StoreOrderApi.getStoreOrder(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as StoreOrderApi.StoreOrderVO
|
||||
await StoreOrderApi.rufundStoreOrder(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
orderId: undefined,
|
||||
extendOrderId: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
userPhone: undefined,
|
||||
userAddress: undefined,
|
||||
cartId: undefined,
|
||||
freightPrice: undefined,
|
||||
totalNum: undefined,
|
||||
totalPrice: undefined,
|
||||
totalPostage: undefined,
|
||||
payPrice: undefined,
|
||||
payPostage: undefined,
|
||||
deductionPrice: undefined,
|
||||
couponId: undefined,
|
||||
couponPrice: undefined,
|
||||
paid: undefined,
|
||||
payTime: undefined,
|
||||
payType: undefined,
|
||||
status: undefined,
|
||||
refundStatus: undefined,
|
||||
refundReasonWapImg: undefined,
|
||||
refundReasonWapExplain: undefined,
|
||||
refundReasonTime: undefined,
|
||||
refundReasonWap: undefined,
|
||||
refundReason: undefined,
|
||||
refundPrice: undefined,
|
||||
deliverySn: undefined,
|
||||
deliveryName: undefined,
|
||||
deliveryType: undefined,
|
||||
deliveryId: undefined,
|
||||
gainIntegral: undefined,
|
||||
useIntegral: undefined,
|
||||
payIntegral: undefined,
|
||||
backIntegral: undefined,
|
||||
mark: undefined,
|
||||
unique: undefined,
|
||||
remark: undefined,
|
||||
merId: undefined,
|
||||
combinationId: undefined,
|
||||
pinkId: undefined,
|
||||
cost: undefined,
|
||||
seckillId: undefined,
|
||||
bargainId: undefined,
|
||||
verifyCode: undefined,
|
||||
storeId: undefined,
|
||||
shippingType: undefined,
|
||||
isChannel: undefined,
|
||||
isSystemDel: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input type="textarea" rows="5" v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as StoreOrderApi from '@/api/mall/order/storeOrder'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
orderId: undefined,
|
||||
extendOrderId: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
userPhone: undefined,
|
||||
userAddress: undefined,
|
||||
cartId: undefined,
|
||||
freightPrice: undefined,
|
||||
totalNum: undefined,
|
||||
totalPrice: undefined,
|
||||
totalPostage: undefined,
|
||||
payPrice: undefined,
|
||||
payPostage: undefined,
|
||||
deductionPrice: undefined,
|
||||
couponId: undefined,
|
||||
couponPrice: undefined,
|
||||
paid: undefined,
|
||||
payTime: undefined,
|
||||
payType: undefined,
|
||||
status: undefined,
|
||||
refundStatus: undefined,
|
||||
refundReasonWapImg: undefined,
|
||||
refundReasonWapExplain: undefined,
|
||||
refundReasonTime: undefined,
|
||||
refundReasonWap: undefined,
|
||||
refundReason: undefined,
|
||||
refundPrice: undefined,
|
||||
deliverySn: undefined,
|
||||
deliveryName: undefined,
|
||||
deliveryType: undefined,
|
||||
deliveryId: undefined,
|
||||
gainIntegral: undefined,
|
||||
useIntegral: undefined,
|
||||
payIntegral: undefined,
|
||||
backIntegral: undefined,
|
||||
mark: undefined,
|
||||
unique: undefined,
|
||||
remark: undefined,
|
||||
merId: undefined,
|
||||
combinationId: undefined,
|
||||
pinkId: undefined,
|
||||
cost: undefined,
|
||||
seckillId: undefined,
|
||||
bargainId: undefined,
|
||||
verifyCode: undefined,
|
||||
storeId: undefined,
|
||||
shippingType: undefined,
|
||||
isChannel: undefined,
|
||||
isSystemDel: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
totalPrice: [{ required: true, message: '订单总价不能为空', trigger: 'blur' }],
|
||||
payPrice: [{ required: true, message: '实际支付金额不能为空', trigger: 'blur' }],
|
||||
gainIntegral: [{ required: true, message: '消费赚取积分不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await StoreOrderApi.getStoreOrder(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as StoreOrderApi.StoreOrderVO
|
||||
await StoreOrderApi.updateStoreOrder(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
orderId: undefined,
|
||||
extendOrderId: undefined,
|
||||
uid: undefined,
|
||||
realName: undefined,
|
||||
userPhone: undefined,
|
||||
userAddress: undefined,
|
||||
cartId: undefined,
|
||||
freightPrice: undefined,
|
||||
totalNum: undefined,
|
||||
totalPrice: undefined,
|
||||
totalPostage: undefined,
|
||||
payPrice: undefined,
|
||||
payPostage: undefined,
|
||||
deductionPrice: undefined,
|
||||
couponId: undefined,
|
||||
couponPrice: undefined,
|
||||
paid: undefined,
|
||||
payTime: undefined,
|
||||
payType: undefined,
|
||||
status: undefined,
|
||||
refundStatus: undefined,
|
||||
refundReasonWapImg: undefined,
|
||||
refundReasonWapExplain: undefined,
|
||||
refundReasonTime: undefined,
|
||||
refundReasonWap: undefined,
|
||||
refundReason: undefined,
|
||||
refundPrice: undefined,
|
||||
deliverySn: undefined,
|
||||
deliveryName: undefined,
|
||||
deliveryType: undefined,
|
||||
deliveryId: undefined,
|
||||
gainIntegral: undefined,
|
||||
useIntegral: undefined,
|
||||
payIntegral: undefined,
|
||||
backIntegral: undefined,
|
||||
mark: undefined,
|
||||
unique: undefined,
|
||||
remark: undefined,
|
||||
merId: undefined,
|
||||
combinationId: undefined,
|
||||
pinkId: undefined,
|
||||
cost: undefined,
|
||||
seckillId: undefined,
|
||||
bargainId: undefined,
|
||||
verifyCode: undefined,
|
||||
storeId: undefined,
|
||||
shippingType: undefined,
|
||||
isChannel: undefined,
|
||||
isSystemDel: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,448 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="全部订单" name=""/>
|
||||
<el-tab-pane label="外卖订单" name="takeout"/>
|
||||
<el-tab-pane label="自取订单" name="takein"/>
|
||||
</el-tabs>
|
||||
<el-form-item label="订单状态:" >
|
||||
<el-radio-group v-model="orderStatus" size="large" fill="#DC143C" @change="queryOrderStatus">
|
||||
<el-radio-button label="">全部</el-radio-button>
|
||||
<el-radio-button label="0">未支付</el-radio-button>
|
||||
<el-radio-button label="1">制作中</el-radio-button>
|
||||
<el-radio-button label="2">待收货</el-radio-button>
|
||||
<el-radio-button label="3">已收货/已取餐</el-radio-button>
|
||||
<!-- <el-radio-button label="4">交易完成</el-radio-button> -->
|
||||
<el-radio-button label="5">退款单</el-radio-button>
|
||||
<el-radio-button label="6">已删除</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="支付方式:" >
|
||||
<el-radio-group v-model="payStatus" size="large" fill="#FF1493" @change="queryPayStatus">
|
||||
<el-radio-button label="">全部</el-radio-button>
|
||||
<el-radio-button label="weixin">微信支付</el-radio-button>
|
||||
<el-radio-button label="alipay">支付宝支付</el-radio-button>
|
||||
<el-radio-button label="yue">余额支付</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="订单号" prop="orderId">
|
||||
<el-input
|
||||
v-model="queryParams.orderId"
|
||||
placeholder="请输入订单号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户姓名" prop="realName">
|
||||
<el-input
|
||||
v-model="queryParams.realName"
|
||||
placeholder="请输入用户姓名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户电话" prop="userPhone">
|
||||
<el-input
|
||||
v-model="queryParams.userPhone"
|
||||
placeholder="请输入用户电话"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<!-- <el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['order:store-order:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" style="width: 100%">
|
||||
<el-table-column label="ID" align="center" prop="id" />
|
||||
<el-table-column label="门店" align="center" prop="shopName" width="100" />
|
||||
<el-table-column label="取餐号" align="center" prop="numberId" />
|
||||
<el-table-column label="桌号" align="center" prop="deskNumber" />
|
||||
<el-table-column label="订单号" align="center" prop="orderId" width="240">
|
||||
<template #default="scope">
|
||||
<span>
|
||||
<el-tag class="ml-2" type="danger" v-if="scope.row.orderType=='desk'">堂食</el-tag>
|
||||
<el-tag class="ml-2" type="danger" v-if="scope.row.orderType=='takeout'">外卖</el-tag>
|
||||
<el-tag class="ml-2" type="danger" v-if="scope.row.orderType=='takein'">自取</el-tag>
|
||||
{{ scope.row.orderId }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户id|昵称" align="center" width="120" >
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.uid }}|{{ scope.row.userRespVO ? scope.row.userRespVO.nickname : '无' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户姓名|电话" align="center" prop="realName" width="150">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.orderType == 'takeout'">{{ scope.row.realName }}|{{ scope.row.userPhone }}</span>
|
||||
<span v-else>无</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品信息" align="center" prop="userAddress" width="350">
|
||||
<template #default="scope">
|
||||
<div class="tabBox" v-for="(val, i ) in scope.row.storeOrderCartInfoDOList" :key="i">
|
||||
<div class="tabBox_img">
|
||||
<img :src="val.image" />
|
||||
</div>
|
||||
<span class="tabBox_tit">{{ val.title + ' - ' }}{{val.spec}}</span>
|
||||
<span class="tabBox_pice">{{ '¥'+ val.price + ' x '+ val.number}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际支付" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.paid == 1">{{ scope.row.payPrice }}</span>
|
||||
<span v-else>未支付</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付方式" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.paid == 1">
|
||||
<span v-if="scope.row.payType=='yue'">余额支付</span>
|
||||
<span v-if="scope.row.payType=='weixin'">微信支付</span>
|
||||
<span v-if="scope.row.payType=='alipay'">支付宝支付</span>
|
||||
</span>
|
||||
<span v-else></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="购买类型" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.orderType=='takeout'">外卖</span>
|
||||
<span v-if="scope.row.orderType=='takein'">自取</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="预约取餐时间"
|
||||
align="center"
|
||||
prop="getTime"
|
||||
:formatter="dateFormatter"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
label="支付时间"
|
||||
align="center"
|
||||
prop="payTime"
|
||||
:formatter="dateFormatter"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column label="订单状态" align="center" prop="statusStr" />
|
||||
<el-table-column
|
||||
label="添加时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="150">
|
||||
<template #default="scope">
|
||||
<div class="flex justify-center items-center">
|
||||
<el-button
|
||||
v-if = "scope.row.statusStr == '未支付'"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('updateOrder', scope.row.id)"
|
||||
v-hasPermi="['order:store-order:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if = "scope.row.statusStr == '未发货'"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('orderSend', scope.row.id)"
|
||||
v-hasPermi="['order:store-order:update']"
|
||||
>
|
||||
出单
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if = "scope.row.statusStr == '退款中'"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('refundOrder', scope.row.id)"
|
||||
v-hasPermi="['order:store-order:update']"
|
||||
>
|
||||
确认退款
|
||||
</el-button>
|
||||
<el-dropdown>
|
||||
<el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if = "scope.row.statusStr == '未支付'" @click="handlePay(scope.row.id)">确认付款</el-dropdown-item>
|
||||
<el-dropdown-item @click="openForm('orderDetail', scope.row.id)">订单详情</el-dropdown-item>
|
||||
<el-dropdown-item @click="openForm('orderRecord', scope.row.id)">订单记录</el-dropdown-item>
|
||||
<el-dropdown-item @click="handleDelete(scope.row.id)">删除订单</el-dropdown-item>
|
||||
<el-dropdown-item v-if = "scope.row.statusStr != '未支付'" @click="openForm('remark', scope.row.id)">订单备注</el-dropdown-item>
|
||||
<el-dropdown-item v-if = "scope.row.statusStr == '待收货'" @click="handleTake(scope.row.id)">后台收货</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<StoreOrderForm ref="formRef" @success="getList" />
|
||||
<OrderSend ref="formRef1" @success="getList" />
|
||||
<OrderSendInfo ref="formRef2" @success="getList" />
|
||||
<StoreOrderRemark ref="formRef3" @success="getList" />
|
||||
<OrderDetail ref="formRef4" />
|
||||
<OrderRecord ref="formRef5" />
|
||||
<StoreOrderRefund ref="formRef6" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="StoreOrder">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as StoreOrderApi from '@/api/mall/order/storeOrder'
|
||||
import StoreOrderForm from './StoreOrderForm.vue'
|
||||
import OrderSend from './OrderSend.vue'
|
||||
import OrderSendInfo from './OrderSendInfo.vue'
|
||||
import StoreOrderRemark from './StoreOrderRemark.vue'
|
||||
import StoreOrderRefund from './StoreOrderRefund.vue'
|
||||
import OrderDetail from './OrderDetail.vue'
|
||||
import OrderRecord from './OrderRecord.vue'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
orderId: "",
|
||||
realName: "",
|
||||
userPhone: "",
|
||||
createTime: [],
|
||||
orderStatus: "",
|
||||
payStatus: "",
|
||||
orderType: ""
|
||||
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
const activeName = ref('')
|
||||
const orderStatus = ref('')
|
||||
const payStatus = ref('')
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab.paneName, event)
|
||||
queryParams.orderType = tab.paneName
|
||||
getList()
|
||||
}
|
||||
|
||||
const queryOrderStatus = (value) => {
|
||||
queryParams.orderStatus = value
|
||||
getList()
|
||||
}
|
||||
const queryPayStatus = (value) => {
|
||||
queryParams.payStatus = value
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await StoreOrderApi.getStoreOrderPage(queryParams)
|
||||
list.value = data.list
|
||||
//console.log("aa:",list.value)
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const formRef1 = ref()
|
||||
const formRef2 = ref()
|
||||
const formRef3 = ref()
|
||||
const formRef4 = ref()
|
||||
const formRef5 = ref()
|
||||
const formRef6 = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
if (type == 'updateOrder') {
|
||||
formRef.value.open(type, id)
|
||||
} else if (type == 'orderSend') {
|
||||
formRef1.value.open(type, id)
|
||||
}else if (type == 'sendInfo') {
|
||||
formRef2.value.open(type, id)
|
||||
}else if (type == 'remark') {
|
||||
formRef3.value.open(type, id)
|
||||
}else if (type == 'orderDetail') {
|
||||
formRef4.value.open(type, id)
|
||||
}else if (type == 'orderRecord') {
|
||||
formRef5.value.open(type, id)
|
||||
}else if (type == 'refundOrder') {
|
||||
formRef6.value.open(type, id)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await StoreOrderApi.deleteStoreOrder(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 确认付款按钮操作 */
|
||||
const handlePay = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.confirm('修改为支付状态')
|
||||
// 发起删除
|
||||
await StoreOrderApi.payStoreOrder(id)
|
||||
message.success(t('common.updateSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 确认收货按钮操作 */
|
||||
const handleTake = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.confirm('修改收货状态')
|
||||
// 发起删除
|
||||
await StoreOrderApi.takeStoreOrder(id)
|
||||
message.success(t('common.updateSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await StoreOrderApi.exportStoreOrder(queryParams)
|
||||
download.excel(data, '订单.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped >
|
||||
img {
|
||||
height: 36px;
|
||||
display: block;
|
||||
}
|
||||
.tabBox{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center
|
||||
}
|
||||
.tabBox_img{
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.tabBox_img img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.tabBox_tit{
|
||||
width :60%;
|
||||
font-size: 12px !important;
|
||||
margin: 0 2px 0 10px;
|
||||
letter-spacing: 1px;
|
||||
padding: 5px 0;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
}
|
||||
.tabBox_pice{
|
||||
width :30%;
|
||||
font-size: 12px !important;
|
||||
margin: 0 2px 0 10px;
|
||||
letter-spacing: 1px;
|
||||
padding: 5px 0;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="全部订单" name="first"/>
|
||||
<el-tab-pane label="普通订单" name="second"/>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="订单号" prop="orderId">
|
||||
<el-input
|
||||
v-model="queryParams.orderId"
|
||||
placeholder="请输入订单号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="取餐号" prop="numberId">
|
||||
<el-input
|
||||
v-model="queryParams.numberId"
|
||||
placeholder="请输入取餐号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['order:store-order:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="6" v-for="(order,k) in list" :key="k">
|
||||
<div >
|
||||
<el-card class="box-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ order.shopName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div>取餐号:{{ order.numberId }}</div>
|
||||
<div>下单时间:{{ formatDate(order.createTime) }}</div>
|
||||
<div>取餐时间:{{ formatDate(order.getTime) }}</div>
|
||||
<div>类型:{{ order.orderType == 'takeout' ? '外卖' : '自取' }}</div>
|
||||
<div>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="openForm('orderDetail', order.id)"
|
||||
v-hasPermi="['order:store-order:update']"
|
||||
>详情</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="openForm('orderSend', order.id)"
|
||||
v-hasPermi="['order:store-order:update']"
|
||||
>出单</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
@click="handleDelete(order.id)"
|
||||
v-hasPermi="['order:store-order:update']"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<StoreOrderForm ref="formRef" @success="getList" />
|
||||
<OrderSend ref="formRef1" @success="getList" />
|
||||
<OrderSendInfo ref="formRef2" @success="getList" />
|
||||
<StoreOrderRemark ref="formRef3" @success="getList" />
|
||||
<OrderDetail ref="formRef4" />
|
||||
<OrderRecord ref="formRef5" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="StoreOrder">
|
||||
// import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as StoreOrderApi from '@/api/mall/order/storeOrder'
|
||||
import StoreOrderForm from './StoreOrderForm.vue'
|
||||
import OrderSend from './OrderSend.vue'
|
||||
import OrderSendInfo from './OrderSendInfo.vue'
|
||||
import StoreOrderRemark from './StoreOrderRemark.vue'
|
||||
import OrderDetail from './OrderDetail.vue'
|
||||
import OrderRecord from './OrderRecord.vue'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
const message = useMessage() // 消息弹窗
|
||||
//const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
orderId: "",
|
||||
realName: "",
|
||||
userPhone: "",
|
||||
createTime: [],
|
||||
orderStatus: 1,
|
||||
payStatus: "",
|
||||
numberId: undefined
|
||||
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
const activeName = ref('first')
|
||||
|
||||
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await StoreOrderApi.getStoreOrderPage(queryParams)
|
||||
list.value = data.list
|
||||
//console.log("aa:",list.value)
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const formRef1 = ref()
|
||||
const formRef2 = ref()
|
||||
const formRef3 = ref()
|
||||
const formRef4 = ref()
|
||||
const formRef5 = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
if (type == 'updateOrder') {
|
||||
formRef.value.open(type, id)
|
||||
} else if (type == 'orderSend') {
|
||||
formRef1.value.open(type, id)
|
||||
}else if (type == 'sendInfo') {
|
||||
formRef2.value.open(type, id)
|
||||
}else if (type == 'remark') {
|
||||
formRef3.value.open(type, id)
|
||||
}else if (type == 'orderDetail') {
|
||||
formRef4.value.open(type, id)
|
||||
}else if (type == 'orderRecord') {
|
||||
formRef5.value.open(type, id)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await StoreOrderApi.deleteStoreOrder(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 确认付款按钮操作 */
|
||||
// const handlePay = async (id: number) => {
|
||||
// try {
|
||||
// // 删除的二次确认
|
||||
// await message.confirm('修改为支付状态')
|
||||
// // 发起删除
|
||||
// await StoreOrderApi.payStoreOrder(id)
|
||||
// message.success(t('common.updateSuccess'))
|
||||
// // 刷新列表
|
||||
// await getList()
|
||||
// } catch {}
|
||||
// }
|
||||
|
||||
// /** 确认收货按钮操作 */
|
||||
// const handleTake = async (id: number) => {
|
||||
// try {
|
||||
// // 删除的二次确认
|
||||
// await message.confirm('修改收货状态')
|
||||
// // 发起删除
|
||||
// await StoreOrderApi.takeStoreOrder(id)
|
||||
// message.success(t('common.updateSuccess'))
|
||||
// // 刷新列表
|
||||
// await getList()
|
||||
// } catch {}
|
||||
// }
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await StoreOrderApi.exportStoreOrder(queryParams)
|
||||
download.excel(data, '订单.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped >
|
||||
img {
|
||||
height: 36px;
|
||||
display: block;
|
||||
}
|
||||
.tabBox{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center
|
||||
}
|
||||
.tabBox_img{
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.tabBox_img img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.tabBox_tit{
|
||||
width :60%;
|
||||
font-size: 12px !important;
|
||||
margin: 0 2px 0 10px;
|
||||
letter-spacing: 1px;
|
||||
padding: 5px 0;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
}
|
||||
.tabBox_pice{
|
||||
width :30%;
|
||||
font-size: 12px !important;
|
||||
margin: 0 2px 0 10px;
|
||||
letter-spacing: 1px;
|
||||
padding: 5px 0;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="80px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="展示店铺" prop="shopId">
|
||||
<el-select
|
||||
v-model="formData.shopId"
|
||||
placeholder="选择店铺"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in shopList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="上级分类" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="formData.parentId"
|
||||
:data="categoryTree"
|
||||
:props="{ label: 'name', value: 'id' }"
|
||||
:render-after-expand="false"
|
||||
placeholder="请选择上级分类"
|
||||
check-strictly
|
||||
default-expand-all
|
||||
/>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="分类名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入分类名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类图片" prop="picUrl">
|
||||
<Materials v-model="formData.picUrl" num="1" type="image" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类排序" prop="sort">
|
||||
<el-input-number v-model="formData.sort" controls-position="right" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开启状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类描述">
|
||||
<el-input v-model="formData.description" type="textarea" placeholder="请输入分类描述" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts" name="ProductCategory">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
// import { handleTree } from '@/utils/tree'
|
||||
import * as ProductCategoryApi from '@/api/mall/product/category'
|
||||
import * as ShopApi from '@/api/mall/store/shop'
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
shopId: null,
|
||||
name: '',
|
||||
picUrl: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
description: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
shopId: [{ required: true, message: '请选择店铺', trigger: 'blur' }],
|
||||
//parentId: [{ required: true, message: '请选择上级分类', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
|
||||
//picUrl: [{ required: true, message: '分类图片不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
// const categoryTree = ref<any[]>([]) // 分类树
|
||||
const shopList = ref([])
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
getList()
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ProductCategoryApi.getCategory(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
// 获得分类树
|
||||
await getTree()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
const getList = async () => {
|
||||
try {
|
||||
const data = await ShopApi.getShopList()
|
||||
shopList.value = data
|
||||
|
||||
} finally {
|
||||
|
||||
}
|
||||
}
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as ProductCategoryApi.CategoryVO
|
||||
if (formType.value === 'create') {
|
||||
await ProductCategoryApi.createCategory(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ProductCategoryApi.updateCategory(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
picUrl: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
description: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// /** 获得分类树 */
|
||||
// const getTree = async () => {
|
||||
// const data = await ProductCategoryApi.getCategoryList({})
|
||||
// const tree = handleTree(data, 'id', 'parentId')
|
||||
// const menu = { id: 0, name: '顶级分类', children: tree }
|
||||
// categoryTree.value = [menu]
|
||||
// }
|
||||
</script>
|
||||
Loaded 100 of 1227 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user