feat: add vue3(element-plus)
This commit is contained in:
14
yudao-ui-admin-vue3/src/views/Error/403.vue
Normal file
14
yudao-ui-admin-vue3/src/views/Error/403.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { Error } from '@/components/Error'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const errorClick = () => {
|
||||
push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Error type="403" @error-click="errorClick" />
|
||||
</template>
|
||||
14
yudao-ui-admin-vue3/src/views/Error/404.vue
Normal file
14
yudao-ui-admin-vue3/src/views/Error/404.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { Error } from '@/components/Error'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const errorClick = () => {
|
||||
push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Error @error-click="errorClick" />
|
||||
</template>
|
||||
14
yudao-ui-admin-vue3/src/views/Error/500.vue
Normal file
14
yudao-ui-admin-vue3/src/views/Error/500.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { Error } from '@/components/Error'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const errorClick = () => {
|
||||
push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Error type="500" @error-click="errorClick" />
|
||||
</template>
|
||||
316
yudao-ui-admin-vue3/src/views/Home/Index.vue
Normal file
316
yudao-ui-admin-vue3/src/views/Home/Index.vue
Normal file
@@ -0,0 +1,316 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { set } from 'lodash-es'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { Echart } from '@/components/Echart'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CountTo } from '@/components/CountTo'
|
||||
import type { AnalysisTotalTypes } from './types'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ElRow, ElCol, ElCard, ElSkeleton } from 'element-plus'
|
||||
import { pieOptions, barOptions, lineOptions } from './echarts-data'
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<el-row :gutter="20" justify="space-between" :class="prefixCls">
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<el-card shadow="hover" class="mb-20px">
|
||||
<el-skeleton :loading="loading" animated :rows="2">
|
||||
<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 icon="svg-icon:peoples" :size="40" />
|
||||
</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
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="102400"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<el-card shadow="hover" class="mb-20px">
|
||||
<el-skeleton :loading="loading" animated :rows="2">
|
||||
<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 icon="svg-icon:message" :size="40" />
|
||||
</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
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="81212"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<el-card shadow="hover" class="mb-20px">
|
||||
<el-skeleton :loading="loading" animated :rows="2">
|
||||
<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 icon="svg-icon:money" :size="40" />
|
||||
</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
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="9280"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<el-card shadow="hover" class="mb-20px">
|
||||
<el-skeleton :loading="loading" animated :rows="2">
|
||||
<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 icon="svg-icon:shopping" :size="40" />
|
||||
</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
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="13600"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" justify="space-between">
|
||||
<el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-20px">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :options="pieOptionsData" :height="300" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-20px">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :options="barOptionsData" :height="300" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-card shadow="hover" class="mb-20px">
|
||||
<el-skeleton :loading="loading" animated :rows="4">
|
||||
<Echart :options="lineOptionsData" :height="350" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<style lang="less" 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>
|
||||
371
yudao-ui-admin-vue3/src/views/Home/Index2.vue
Normal file
371
yudao-ui-admin-vue3/src/views/Home/Index2.vue
Normal file
@@ -0,0 +1,371 @@
|
||||
<script setup lang="ts">
|
||||
import { useTimeAgo } from '@/hooks/web/useTimeAgo'
|
||||
import { ElRow, ElCol, ElSkeleton, ElCard, ElDivider, ElLink } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { CountTo } from '@/components/CountTo'
|
||||
import { formatTime } from '@/utils'
|
||||
import { Echart } from '@/components/Echart'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { radarOption } from './echarts-data'
|
||||
import { Highlight } from '@/components/Highlight'
|
||||
import type { WorkplaceTotal, Project, Dynamic, Team } from './types'
|
||||
import { set } from 'lodash-es'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { wsCache } = useCache()
|
||||
const loading = ref(true)
|
||||
const avatar = wsCache.get('user').user.avatar
|
||||
const username = wsCache.get('user').user.nickname
|
||||
// 获取统计数
|
||||
let totalSate = reactive<WorkplaceTotal>({
|
||||
project: 0,
|
||||
access: 0,
|
||||
todo: 0
|
||||
})
|
||||
|
||||
const getCount = async () => {
|
||||
const data = {
|
||||
project: 40,
|
||||
access: 2340,
|
||||
todo: 10
|
||||
}
|
||||
totalSate = Object.assign(totalSate, data)
|
||||
}
|
||||
|
||||
let projects = reactive<Project[]>([])
|
||||
|
||||
// 获取项目数
|
||||
const getProject = async () => {
|
||||
const data = [
|
||||
{
|
||||
name: 'Github',
|
||||
icon: 'akar-icons:github-fill',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Vue',
|
||||
icon: 'logos:vue',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Angular',
|
||||
icon: 'logos:angular-icon',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
icon: 'logos:react',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Webpack',
|
||||
icon: 'logos:webpack',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Vite',
|
||||
icon: 'vscode-icons:file-type-vite',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
}
|
||||
]
|
||||
projects = Object.assign(projects, data)
|
||||
}
|
||||
|
||||
// 获取动态
|
||||
let dynamics = reactive<Dynamic[]>([])
|
||||
|
||||
const getDynamic = async () => {
|
||||
const data = [
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
}
|
||||
]
|
||||
dynamics = Object.assign(dynamics, data)
|
||||
}
|
||||
|
||||
// 获取团队
|
||||
let team = reactive<Team[]>([])
|
||||
|
||||
const getTeam = async () => {
|
||||
const data = [
|
||||
{
|
||||
name: 'Github',
|
||||
icon: 'akar-icons:github-fill'
|
||||
},
|
||||
{
|
||||
name: 'Vue',
|
||||
icon: 'logos:vue'
|
||||
},
|
||||
{
|
||||
name: 'Angular',
|
||||
icon: 'logos:angular-icon'
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
icon: 'logos:react'
|
||||
},
|
||||
{
|
||||
name: 'Webpack',
|
||||
icon: 'logos:webpack'
|
||||
},
|
||||
{
|
||||
name: 'Vite',
|
||||
icon: 'vscode-icons:file-type-vite'
|
||||
}
|
||||
]
|
||||
team = Object.assign(team, data)
|
||||
}
|
||||
|
||||
// 获取指数
|
||||
let radarOptionData = reactive<EChartsOption>(radarOption) as EChartsOption
|
||||
|
||||
const getRadar = async () => {
|
||||
const data = [
|
||||
{ name: 'workplace.quote', max: 65, personal: 42, team: 50 },
|
||||
{ name: 'workplace.contribution', max: 160, personal: 30, team: 140 },
|
||||
{ name: 'workplace.hot', max: 300, personal: 20, team: 28 },
|
||||
{ name: 'workplace.yield', max: 130, personal: 35, team: 35 },
|
||||
{ name: 'workplace.follow', max: 100, personal: 80, team: 90 }
|
||||
]
|
||||
set(
|
||||
radarOptionData,
|
||||
'radar.indicator',
|
||||
data.map((v) => {
|
||||
return {
|
||||
name: t(v.name),
|
||||
max: v.max
|
||||
}
|
||||
})
|
||||
)
|
||||
set(radarOptionData, 'series', [
|
||||
{
|
||||
name: '指数',
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: data.map((v) => v.personal),
|
||||
name: t('workplace.personal')
|
||||
},
|
||||
{
|
||||
value: data.map((v) => v.team),
|
||||
name: t('workplace.team')
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
const getAllApi = async () => {
|
||||
await Promise.all([getCount(), getProject(), getDynamic(), getTeam(), getRadar()])
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
getAllApi()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-card shadow="never">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<el-row :gutter="20" justify="space-between">
|
||||
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex items-center">
|
||||
<img :src="avatar" alt="" class="w-70px h-70px rounded-[50%] mr-20px" />
|
||||
<div>
|
||||
<div class="text-20px text-700">
|
||||
{{ t('workplace.goodMorning') }} {{ username }} {{ t('workplace.happyDay') }}
|
||||
</div>
|
||||
<div class="mt-10px text-14px text-gray-500">
|
||||
{{ t('workplace.toady') }},20℃ - 32℃!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex h-70px items-center justify-end <sm:mt-20px">
|
||||
<div class="px-8px text-right">
|
||||
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</div>
|
||||
<CountTo
|
||||
class="text-20px"
|
||||
:start-val="0"
|
||||
:end-val="totalSate.project"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
<el-divider direction="vertical" />
|
||||
<div class="px-8px text-right">
|
||||
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.toDo') }}</div>
|
||||
<CountTo
|
||||
class="text-20px"
|
||||
:start-val="0"
|
||||
:end-val="totalSate.todo"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<div class="px-8px text-right">
|
||||
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.access') }}</div>
|
||||
<CountTo
|
||||
class="text-20px"
|
||||
:start-val="0"
|
||||
:end-val="totalSate.access"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-row class="mt-20px" :gutter="20" justify="space-between">
|
||||
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-20px">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('workplace.project') }}</span>
|
||||
<ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>
|
||||
</div>
|
||||
</template>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<el-row>
|
||||
<el-col
|
||||
v-for="(item, index) in projects"
|
||||
:key="`card-${index}`"
|
||||
:xl="8"
|
||||
:lg="8"
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
>
|
||||
<el-card shadow="hover">
|
||||
<div class="flex items-center">
|
||||
<Icon :icon="item.icon" :size="25" class="mr-10px" />
|
||||
<span class="text-16px">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="mt-15px text-14px text-gray-400">{{ t(item.message) }}</div>
|
||||
<div class="mt-20px text-12px text-gray-400 flex justify-between">
|
||||
<span>{{ item.personal }}</span>
|
||||
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt-20px">
|
||||
<template #header>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('workplace.dynamic') }}</span>
|
||||
<ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>
|
||||
</div>
|
||||
</template>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<div v-for="(item, index) in dynamics" :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">
|
||||
<Highlight :keys="item.keys.map((v) => t(v))">
|
||||
{{ username }} {{ t('workplace.pushCode') }}
|
||||
</Highlight>
|
||||
</div>
|
||||
<div class="mt-15px text-12px text-gray-400">
|
||||
{{ useTimeAgo(item.time) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
</div>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-20px">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>{{ t('workplace.shortcutOperation') }}</span>
|
||||
</template>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<el-col
|
||||
v-for="item in 9"
|
||||
:key="`card-${item}`"
|
||||
:xl="12"
|
||||
:lg="12"
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
class="mb-10px"
|
||||
>
|
||||
<ElLink type="default" :underline="false">
|
||||
{{ t('workplace.operation') }}{{ item }}
|
||||
</ElLink>
|
||||
</el-col>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt-20px">
|
||||
<template #header>
|
||||
<span>{{ t('workplace.index') }}</span>
|
||||
</template>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :options="radarOptionData" :height="400" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt-20px">
|
||||
<template #header>
|
||||
<span>{{ t('workplace.team') }}</span>
|
||||
</template>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<el-row>
|
||||
<el-col v-for="item in team" :key="`team-${item.name}`" :span="12" class="mb-20px">
|
||||
<div class="flex items-center">
|
||||
<Icon :icon="item.icon" class="mr-10px" />
|
||||
<ElLink type="default" :underline="false">
|
||||
{{ item.name }}
|
||||
</ElLink>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
310
yudao-ui-admin-vue3/src/views/Home/echarts-data.ts
Normal file
310
yudao-ui-admin-vue3/src/views/Home/echarts-data.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { EChartsOption as EChartsWordOption } from 'echarts-wordcloud'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
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: EChartsWordOption = {
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
52
yudao-ui-admin-vue3/src/views/Home/types.ts
Normal file
52
yudao-ui-admin-vue3/src/views/Home/types.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
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 Dynamic = {
|
||||
keys: string[]
|
||||
time: Date | number | string
|
||||
}
|
||||
|
||||
export type Team = {
|
||||
name: string
|
||||
icon: 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
|
||||
}
|
||||
96
yudao-ui-admin-vue3/src/views/Login/Login.vue
Normal file
96
yudao-ui-admin-vue3/src/views/Login/Login.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<script setup lang="ts">
|
||||
import { LoginForm } from './components'
|
||||
import { MobileForm } from './components'
|
||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||
import { LocaleDropdown } from '@/components/LocaleDropdown'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { underlineToHump } from '@/utils'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('login')
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="prefixCls"
|
||||
class="h-[100%] relative overflow-hidden <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 src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
|
||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||
</div>
|
||||
<!-- 左边的背景图 + 欢迎语 -->
|
||||
<div class="flex justify-center items-center h-[calc(100%-60px)]">
|
||||
<TransitionGroup
|
||||
appear
|
||||
tag="div"
|
||||
enter-active-class="animate__animated animate__bounceInLeft"
|
||||
>
|
||||
<img src="@/assets/svgs/login-box-bg.svg" key="1" alt="" class="w-350px" />
|
||||
<div class="text-3xl text-white" key="2">{{ t('login.welcome') }}</div>
|
||||
<div class="mt-5 font-normal text-white text-14px" key="3">
|
||||
{{ 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 src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
|
||||
<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)" />
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" 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>
|
||||
281
yudao-ui-admin-vue3/src/views/Login/components/LoginForm.vue
Normal file
281
yudao-ui-admin-vue3/src/views/Login/components/LoginForm.vue
Normal file
@@ -0,0 +1,281 @@
|
||||
<script lang="ts" setup>
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElCheckbox,
|
||||
ElCol,
|
||||
ElLink,
|
||||
ElRow,
|
||||
ElDivider
|
||||
} from 'element-plus'
|
||||
import { reactive, ref, unref, onMounted, computed, watch } from 'vue'
|
||||
import { getCodeImgApi, getTenantIdByNameApi, loginApi, getAsyncRoutesApi } from '@/api/login'
|
||||
import { setToken } from '@/utils/auth'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { LoginStateEnum, useLoginState, useFormValid } from './useLogin'
|
||||
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
||||
|
||||
const { currentRoute, addRoute, push } = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
const userStore = useUserStoreWithOut()
|
||||
const formLogin = ref()
|
||||
const { validForm } = useFormValid(formLogin)
|
||||
const { wsCache } = useCache()
|
||||
const { setLoginState, getLoginState } = useLoginState()
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||
const iconSize = 30
|
||||
const iconColor = '#999'
|
||||
const redirect = ref<string>('')
|
||||
const { t } = useI18n()
|
||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||
const iconLock = useIcon({ icon: 'ep:lock' })
|
||||
const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
|
||||
const remember = ref(false)
|
||||
const LoginRules = {
|
||||
tenantName: [required],
|
||||
username: [required],
|
||||
password: [required],
|
||||
code: [required]
|
||||
}
|
||||
const loginLoading = ref(false)
|
||||
const loginData = reactive({
|
||||
codeImg: '',
|
||||
isShowPassword: false,
|
||||
captchaEnable: true,
|
||||
tenantEnable: true,
|
||||
token: '',
|
||||
loading: {
|
||||
signIn: false
|
||||
},
|
||||
loginForm: {
|
||||
tenantName: '芋道源码',
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
code: '',
|
||||
uuid: ''
|
||||
}
|
||||
})
|
||||
|
||||
// 获取验证码
|
||||
const getCode = async () => {
|
||||
const res = await getCodeImgApi()
|
||||
loginData.codeImg = 'data:image/gif;base64,' + res.img
|
||||
loginData.loginForm.uuid = res.uuid
|
||||
}
|
||||
//获取租户ID
|
||||
const getTenantId = async () => {
|
||||
const res = await getTenantIdByNameApi(loginData.loginForm.tenantName)
|
||||
wsCache.set('tenantId', res)
|
||||
}
|
||||
// 登录
|
||||
const handleLogin = async () => {
|
||||
await getTenantId()
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
loginLoading.value = true
|
||||
await loginApi(loginData.loginForm)
|
||||
.then(async (res) => {
|
||||
setToken(res)
|
||||
await userStore.getUserInfoAction()
|
||||
await getRoutes()
|
||||
})
|
||||
.catch(() => {
|
||||
getCode()
|
||||
})
|
||||
.finally(() => {
|
||||
loginLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 获取路由
|
||||
const getRoutes = async () => {
|
||||
// 后端过滤菜单
|
||||
const routers = await getAsyncRoutesApi()
|
||||
wsCache.set('roleRouters', routers)
|
||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
permissionStore.setIsAddRouters(true)
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
}
|
||||
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
redirect.value = route?.query?.redirect as string
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
onMounted(() => {
|
||||
getCode()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<el-form
|
||||
:model="loginData.loginForm"
|
||||
:rules="LoginRules"
|
||||
label-position="top"
|
||||
class="login-form"
|
||||
label-width="120px"
|
||||
size="large"
|
||||
v-show="getShow"
|
||||
ref="formLogin"
|
||||
>
|
||||
<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 prop="tenantName">
|
||||
<el-input
|
||||
type="text"
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
/>
|
||||
</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"
|
||||
type="password"
|
||||
:placeholder="t('login.password')"
|
||||
show-password
|
||||
@keyup.enter="handleLogin"
|
||||
:prefix-icon="iconLock"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item prop="code">
|
||||
<el-row justify="space-between" style="width: 100%">
|
||||
<el-col :span="14">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.code"
|
||||
:placeholder="t('login.code')"
|
||||
@keyup.enter="handleLogin"
|
||||
:prefix-icon="iconCircleCheck"
|
||||
style="width: 90%"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<div class="login-code">
|
||||
<img :src="loginData.codeImg" @click="getCode" class="login-code-img" alt="" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</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="remember">{{ t('login.remember') }}</el-checkbox>
|
||||
</el-col>
|
||||
<el-col :span="12" :offset="6">
|
||||
<el-link type="primary" style="float: right">{{ t('login.forgetPassword') }}</el-link>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<el-button :loading="loginLoading" type="primary" class="w-[100%]" @click="handleLogin">{{
|
||||
t('login.login')
|
||||
}}</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<el-row justify="space-between" style="width: 100%" :gutter="5">
|
||||
<el-col :span="8">
|
||||
<el-button class="w-[100%]" @click="setLoginState(LoginStateEnum.MOBILE)">{{
|
||||
t('login.btnMobile')
|
||||
}}</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button class="w-[100%]">{{ t('login.btnQRCode') }}</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button class="w-[100%]">{{ t('login.btnRegister') }}</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<div class="flex justify-between w-[100%]">
|
||||
<Icon
|
||||
icon="ant-design:github-filled"
|
||||
:size="iconSize"
|
||||
class="cursor-pointer anticon"
|
||||
:color="iconColor"
|
||||
/>
|
||||
<Icon
|
||||
icon="ant-design:wechat-filled"
|
||||
:size="iconSize"
|
||||
class="cursor-pointer anticon"
|
||||
:color="iconColor"
|
||||
/>
|
||||
<Icon
|
||||
icon="ant-design:alipay-circle-filled"
|
||||
:size="iconSize"
|
||||
:color="iconColor"
|
||||
class="cursor-pointer anticon"
|
||||
/>
|
||||
<Icon
|
||||
icon="ant-design:weibo-circle-filled"
|
||||
:size="iconSize"
|
||||
:color="iconColor"
|
||||
class="cursor-pointer anticon"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.login-code {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
float: right;
|
||||
|
||||
img {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
max-width: 100px;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
279
yudao-ui-admin-vue3/src/views/Login/components/LoginFormOld.vue
Normal file
279
yudao-ui-admin-vue3/src/views/Login/components/LoginFormOld.vue
Normal file
@@ -0,0 +1,279 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, unref, watch, onMounted } from 'vue'
|
||||
import { Form } from '@/components/Form'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElCheckbox, ElLink } from 'element-plus'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { getTenantIdByNameApi, getCodeImgApi, loginApi, getAsyncRoutesApi } from '@/api/login'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { setToken } from '@/utils/auth'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
||||
import { UserLoginVO } from '@/api/login/types'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
const userStore = useUserStoreWithOut()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const { currentRoute, addRoute, push } = useRouter()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules = {
|
||||
tenantName: [required],
|
||||
username: [required],
|
||||
password: [required],
|
||||
code: [required]
|
||||
}
|
||||
const loginData = reactive({
|
||||
codeImg: '',
|
||||
isShowPassword: false,
|
||||
captchaEnable: true,
|
||||
tenantEnable: true,
|
||||
token: '',
|
||||
loading: {
|
||||
signIn: false
|
||||
},
|
||||
loginForm: {
|
||||
tenantName: '芋道源码',
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
code: '',
|
||||
uuid: ''
|
||||
}
|
||||
})
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'title',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'tenantName',
|
||||
label: t('login.tenantname'),
|
||||
value: loginData.loginForm.tenantName,
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: t('login.tenantNamePlaceholder')
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'username',
|
||||
label: t('login.username'),
|
||||
value: loginData.loginForm.username,
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: t('login.usernamePlaceholder')
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'password',
|
||||
label: t('login.password'),
|
||||
value: loginData.loginForm.password,
|
||||
component: 'InputPassword',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
placeholder: t('login.passwordPlaceholder')
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
label: t('login.code'),
|
||||
value: loginData.loginForm.code,
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
},
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
placeholder: t('login.codePlaceholder')
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'codeImg',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'tool',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'login',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'other',
|
||||
component: 'Divider',
|
||||
label: t('login.otherLogin'),
|
||||
componentProps: {
|
||||
contentPosition: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'otherIcon',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const iconSize = 30
|
||||
const remember = ref(false)
|
||||
const { register, elFormRef, methods } = useForm()
|
||||
const loading = ref(false)
|
||||
const iconColor = '#999'
|
||||
const redirect = ref<string>('')
|
||||
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
redirect.value = route?.query?.redirect as string
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
// 获取验证码
|
||||
const getCode = async () => {
|
||||
const res = await getCodeImgApi()
|
||||
loginData.codeImg = 'data:image/gif;base64,' + res.img
|
||||
loginData.loginForm.uuid = res.uuid
|
||||
}
|
||||
//获取租户ID
|
||||
const getTenantId = async () => {
|
||||
const res = await getTenantIdByNameApi(loginData.loginForm.tenantName)
|
||||
wsCache.set('tenantId', res)
|
||||
}
|
||||
// 登录
|
||||
const signIn = async () => {
|
||||
await getTenantId()
|
||||
const formRef = unref(elFormRef)
|
||||
await formRef?.validate(async (isValid) => {
|
||||
if (isValid) {
|
||||
loading.value = true
|
||||
const { getFormData } = methods
|
||||
const formData = await getFormData<UserLoginVO>()
|
||||
formData.uuid = loginData.loginForm.uuid
|
||||
await loginApi(formData)
|
||||
.then(async (res) => {
|
||||
setToken(res)
|
||||
getRoutes()
|
||||
await userStore.getUserInfoAction()
|
||||
})
|
||||
.catch(() => {
|
||||
getCode()
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
}
|
||||
})
|
||||
}
|
||||
// 获取路由
|
||||
const getRoutes = async () => {
|
||||
// 后端过滤菜单
|
||||
const routers = await getAsyncRoutesApi()
|
||||
wsCache.set('roleRouters', routers)
|
||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
permissionStore.setIsAddRouters(true)
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
}
|
||||
onMounted(() => {
|
||||
getCode()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
:schema="schema"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
hide-required-asterisk
|
||||
size="large"
|
||||
@register="register"
|
||||
v-show="false"
|
||||
>
|
||||
<template #header>
|
||||
<h2 class="text-2xl font-bold text-center w-[100%]">{{ t('login.login') }}</h2>
|
||||
</template>
|
||||
<template #codeImg>
|
||||
<img :src="loginData.codeImg" @click="getCode" alt="" />
|
||||
</template>
|
||||
|
||||
<template #tool>
|
||||
<div class="flex justify-between items-center w-[100%]">
|
||||
<ElCheckbox v-model="remember" :label="t('login.remember')" size="small" />
|
||||
<ElLink type="primary" :underline="false">{{ t('login.forgetPassword') }}</ElLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #login>
|
||||
<ElButton :loading="loading" type="primary" class="w-[100%]" @click="signIn">
|
||||
{{ t('login.login') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
|
||||
<template #otherIcon>
|
||||
<div class="flex justify-between w-[100%]">
|
||||
<Icon
|
||||
icon="ant-design:github-filled"
|
||||
:size="iconSize"
|
||||
class="cursor-pointer anticon"
|
||||
:color="iconColor"
|
||||
/>
|
||||
<Icon
|
||||
icon="ant-design:wechat-filled"
|
||||
:size="iconSize"
|
||||
class="cursor-pointer anticon"
|
||||
:color="iconColor"
|
||||
/>
|
||||
<Icon
|
||||
icon="ant-design:alipay-circle-filled"
|
||||
:size="iconSize"
|
||||
:color="iconColor"
|
||||
class="cursor-pointer anticon"
|
||||
/>
|
||||
<Icon
|
||||
icon="ant-design:weibo-circle-filled"
|
||||
:size="iconSize"
|
||||
:color="iconColor"
|
||||
class="cursor-pointer anticon"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.anticon) {
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||
|
||||
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')
|
||||
}
|
||||
return titleObj[unref(getLoginState)]
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<h2 class="mb-3 text-2xl font-bold text-center xl:text-3xl enter-x xl:text-center">
|
||||
{{ getFormTitle }}
|
||||
</h2>
|
||||
</template>
|
||||
248
yudao-ui-admin-vue3/src/views/Login/components/MobileForm.vue
Normal file
248
yudao-ui-admin-vue3/src/views/Login/components/MobileForm.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<script setup lang="ts">
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
import { reactive, ref, unref, watch, onMounted, computed } from 'vue'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { ElForm, ElFormItem, ElInput, ElRow, ElCol, ElMessage } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import {
|
||||
getTenantIdByNameApi,
|
||||
getCodeImgApi,
|
||||
getAsyncRoutesApi,
|
||||
sendSmsCodeApi,
|
||||
smsLoginApi
|
||||
} from '@/api/login'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { setToken } from '@/utils/auth'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
||||
import { useLoginState, LoginStateEnum, useFormValid } from './useLogin'
|
||||
const formSmsLogin = ref()
|
||||
const { validForm } = useFormValid(formSmsLogin)
|
||||
const { handleBackLogin, getLoginState } = useLoginState()
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.MOBILE)
|
||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||
const iconCellphone = useIcon({ icon: 'ep:cellphone' })
|
||||
const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
|
||||
const { wsCache } = useCache()
|
||||
const userStore = useUserStoreWithOut()
|
||||
const permissionStore = usePermissionStore()
|
||||
const { currentRoute, addRoute, push } = useRouter()
|
||||
const loginLoading = ref(false)
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules = {
|
||||
tenantName: [required],
|
||||
mobileNumber: [required],
|
||||
code: [required]
|
||||
}
|
||||
const loginData = reactive({
|
||||
codeImg: '',
|
||||
// TODO @jinz:多余的变量 isShowPassword、captchaEnable
|
||||
isShowPassword: false,
|
||||
captchaEnable: true,
|
||||
tenantEnable: true,
|
||||
token: '',
|
||||
loading: {
|
||||
signIn: false
|
||||
},
|
||||
loginForm: {
|
||||
uuid: '',
|
||||
tenantName: '芋道源码',
|
||||
mobileNumber: '',
|
||||
code: ''
|
||||
}
|
||||
})
|
||||
// TODO @jinz:smsVO 小写哈
|
||||
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
|
||||
console.log('getSmsCode begin:', SmsVO.smsCode)
|
||||
await sendSmsCodeApi(SmsVO.smsCode)
|
||||
.then(async (res) => {
|
||||
// 提示验证码发送成功
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('login.SmsSendMsg')
|
||||
})
|
||||
console.log('res', res)
|
||||
// 设置倒计时
|
||||
mobileCodeTimer.value = 60
|
||||
let msgTimer = setInterval(() => {
|
||||
mobileCodeTimer.value = mobileCodeTimer.value - 1
|
||||
if (mobileCodeTimer.value <= 0) {
|
||||
clearInterval(msgTimer)
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
.catch(() => {
|
||||
console.log('error')
|
||||
})
|
||||
}
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
redirect.value = route?.query?.redirect as string
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
// 获取验证码 TODO @jinz:是不是可以去掉?手机这里暂时不用验证码
|
||||
const getCode = async () => {
|
||||
const res = await getCodeImgApi()
|
||||
loginData.codeImg = 'data:image/gif;base64,' + res.img
|
||||
loginData.loginForm.uuid = res.uuid
|
||||
}
|
||||
// 获取租户 ID
|
||||
const getTenantId = async () => {
|
||||
const res = await getTenantIdByNameApi(loginData.loginForm.tenantName)
|
||||
wsCache.set('tenantId', res)
|
||||
}
|
||||
// 登录
|
||||
const signIn = async () => {
|
||||
await getTenantId()
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
loginLoading.value = true
|
||||
SmsVO.loginSms.mobile = loginData.loginForm.mobileNumber
|
||||
SmsVO.loginSms.code = loginData.loginForm.code
|
||||
await smsLoginApi(SmsVO.loginSms)
|
||||
.then(async (res) => {
|
||||
setToken(res?.token)
|
||||
await userStore.getUserInfoAction()
|
||||
getRoutes()
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loginLoading.value = false
|
||||
})
|
||||
}
|
||||
// 获取路由
|
||||
const getRoutes = async () => {
|
||||
// 后端过滤菜单
|
||||
// TODO @jinz:这块 getRoutes 的代码,是不是可以统一到 store 里,类似 ruoyi-vue 的做法,可能要找作者沟通下
|
||||
const routers = await getAsyncRoutesApi()
|
||||
wsCache.set('roleRouters', routers)
|
||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
permissionStore.setIsAddRouters(true)
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
}
|
||||
onMounted(() => {
|
||||
getCode()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<el-form
|
||||
:model="loginData.loginForm"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
class="login-form"
|
||||
label-width="120px"
|
||||
size="large"
|
||||
v-show="getShow"
|
||||
ref="formSmsLogin"
|
||||
>
|
||||
<el-row style="margin-left: -10px; margin-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 prop="tenantName">
|
||||
<el-input
|
||||
type="text"
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 手机号 -->
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 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-left: 10px; padding-right: 10px">
|
||||
<el-form-item prop="code">
|
||||
<el-row justify="space-between" style="width: 100%" :gutter="5">
|
||||
<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"
|
||||
@click="getSmsCode"
|
||||
class="getMobileCode"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
{{ 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-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<el-button :loading="loginLoading" type="primary" class="w-[100%]" @click="signIn">
|
||||
{{ t('login.login') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<el-button :loading="loginLoading" class="w-[100%]" @click="handleBackLogin">
|
||||
{{ t('login.backLogin') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
:deep(.anticon) {
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.smsbtn {
|
||||
margin-top: 33px;
|
||||
}
|
||||
</style>
|
||||
4
yudao-ui-admin-vue3/src/views/Login/components/index.ts
Normal file
4
yudao-ui-admin-vue3/src/views/Login/components/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as LoginForm } from './LoginForm.vue'
|
||||
export { default as MobileForm } from './MobileForm.vue'
|
||||
export { default as LoginFormOld } from './LoginFormOld.vue' // TODO jinz:old 是不是可以删除哈,git 可以管理的
|
||||
export { default as LoginFormTitle } from './LoginFormTitle.vue'
|
||||
136
yudao-ui-admin-vue3/src/views/Login/components/useLogin.ts
Normal file
136
yudao-ui-admin-vue3/src/views/Login/components/useLogin.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { ref, computed, unref, Ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
export enum LoginStateEnum {
|
||||
LOGIN,
|
||||
REGISTER,
|
||||
RESET_PASSWORD,
|
||||
MOBILE,
|
||||
QR_CODE
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @jinz:多余的,是不是可以删除哈
|
||||
export function useFormRules(formData?: Recordable) {
|
||||
const { t } = useI18n()
|
||||
|
||||
const getAccountFormRule = computed(() => createRule(t('sys.login.accountPlaceholder')))
|
||||
const getPasswordFormRule = computed(() => createRule(t('sys.login.passwordPlaceholder')))
|
||||
const getSmsFormRule = computed(() => createRule(t('sys.login.smsPlaceholder')))
|
||||
const getMobileFormRule = computed(() => createRule(t('sys.login.mobilePlaceholder')))
|
||||
|
||||
const validatePolicy = async (_: RuleObject, value: boolean) => {
|
||||
return !value ? Promise.reject(t('sys.login.policyPlaceholder')) : Promise.resolve()
|
||||
}
|
||||
|
||||
const validateConfirmPassword = (password: string) => {
|
||||
return async (_: RuleObject, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject(t('sys.login.passwordPlaceholder'))
|
||||
}
|
||||
if (value !== password) {
|
||||
return Promise.reject(t('sys.login.diffPwd'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
const getFormRules = computed(
|
||||
(): {
|
||||
[k: string]: ValidationRule | ValidationRule[]
|
||||
} => {
|
||||
const accountFormRule = unref(getAccountFormRule)
|
||||
const passwordFormRule = unref(getPasswordFormRule)
|
||||
const smsFormRule = unref(getSmsFormRule)
|
||||
const mobileFormRule = unref(getMobileFormRule)
|
||||
|
||||
const mobileRule = {
|
||||
sms: smsFormRule,
|
||||
mobile: mobileFormRule
|
||||
}
|
||||
switch (unref(currentState)) {
|
||||
// register form rules
|
||||
case LoginStateEnum.REGISTER:
|
||||
return {
|
||||
account: accountFormRule,
|
||||
password: passwordFormRule,
|
||||
confirmPassword: [
|
||||
{
|
||||
validator: validateConfirmPassword(formData?.password),
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
policy: [
|
||||
{
|
||||
validator: validatePolicy,
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
...mobileRule
|
||||
}
|
||||
|
||||
// reset password form rules
|
||||
case LoginStateEnum.RESET_PASSWORD:
|
||||
return {
|
||||
account: accountFormRule,
|
||||
...mobileRule
|
||||
}
|
||||
|
||||
// mobile form rules
|
||||
case LoginStateEnum.MOBILE:
|
||||
return mobileRule
|
||||
|
||||
// login form rules
|
||||
default:
|
||||
return {
|
||||
account: accountFormRule,
|
||||
password: passwordFormRule
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
return {
|
||||
getFormRules
|
||||
}
|
||||
}
|
||||
|
||||
function createRule(message: string) {
|
||||
return [
|
||||
{
|
||||
required: true,
|
||||
message,
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
62
yudao-ui-admin-vue3/src/views/Profile/Index.vue
Normal file
62
yudao-ui-admin-vue3/src/views/Profile/Index.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElCard, ElTabs, ElTabPane } from 'element-plus'
|
||||
import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components/'
|
||||
const { t } = useI18n()
|
||||
const activeName = ref('basicInfo')
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex">
|
||||
<el-card class="w-1/3 user" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ t('profile.user.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<ProfileUser />
|
||||
</el-card>
|
||||
<el-card class="w-2/3 user" style="margin-left: 10px" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ t('profile.info.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-tabs v-model="activeName" tab-position="top" style="height: 400px" class="profile-tabs">
|
||||
<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 />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.user {
|
||||
max-height: 960px;
|
||||
padding: 15px 20px 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;
|
||||
color: #6b778c;
|
||||
font-weight: 600;
|
||||
}
|
||||
.el-tabs--left .el-tabs__content {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import type { FormRules, FormInstance } from 'element-plus'
|
||||
import { ElForm, ElFormItem, ElInput, ElRadioGroup, ElRadio, ElMessage } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { getUserProfileApi, updateUserProfileApi } from '@/api/system/user/profile'
|
||||
const { t } = useI18n()
|
||||
const formRef = ref<FormInstance>()
|
||||
interface BasicUserInfoVO {
|
||||
id: number
|
||||
nickname: string
|
||||
email: string
|
||||
mobile: string
|
||||
sex: number
|
||||
}
|
||||
interface userInfoType {
|
||||
basicUserInfo: BasicUserInfoVO
|
||||
}
|
||||
const user = reactive<userInfoType>({
|
||||
basicUserInfo: {
|
||||
id: 0,
|
||||
nickname: '',
|
||||
mobile: '',
|
||||
email: '',
|
||||
sex: 0
|
||||
}
|
||||
})
|
||||
// 表单校验
|
||||
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 submit = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
await updateUserProfileApi({ params: user.basicUserInfo })
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
})
|
||||
}
|
||||
const reset = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await getUserInfo()
|
||||
}
|
||||
const getUserInfo = async () => {
|
||||
const users = await getUserProfileApi()
|
||||
user.basicUserInfo = users
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getUserInfo()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<el-form ref="form" :model="user.basicUserInfo" :rules="rules" label-width="80px">
|
||||
<el-form-item :label="t('profile.user.nickname')" prop="nickname">
|
||||
<el-input v-model="user.basicUserInfo.nickname" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('profile.user.mobile')" prop="mobile">
|
||||
<el-input v-model="user.basicUserInfo.mobile" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('profile.user.email')" prop="email">
|
||||
<el-input v-model="user.basicUserInfo.email" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('profile.user.sex')" prop="sex">
|
||||
<el-radio-group v-model="user.basicUserInfo.sex">
|
||||
<el-radio :label="1">{{ t('profile.user.man') }}</el-radio>
|
||||
<el-radio :label="2">{{ t('profile.user.woman') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit(formRef)">{{ t('common.save') }}</el-button>
|
||||
<el-button type="danger" @click="reset(formRef)">{{ t('common.reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
113
yudao-ui-admin-vue3/src/views/Profile/components/ProfileUser.vue
Normal file
113
yudao-ui-admin-vue3/src/views/Profile/components/ProfileUser.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<script setup lang="ts">
|
||||
import { getUserProfileApi } from '@/api/system/user/profile'
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { UserAvatarVue } from './'
|
||||
import { ProfileVO } from '@/api/system/user/profile/types'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
const { t } = useI18n()
|
||||
interface userInfoType {
|
||||
user: ProfileVO
|
||||
}
|
||||
const userInfo = reactive<userInfoType>({
|
||||
user: {
|
||||
id: 0,
|
||||
username: '',
|
||||
nickname: '',
|
||||
dept: {
|
||||
id: 0,
|
||||
name: ''
|
||||
},
|
||||
roles: [],
|
||||
posts: [],
|
||||
socialUsers: [],
|
||||
email: '',
|
||||
mobile: '',
|
||||
sex: 0,
|
||||
avatar: '',
|
||||
status: 0,
|
||||
remark: '',
|
||||
loginIp: '',
|
||||
loginDate: new Date(),
|
||||
createTime: new Date()
|
||||
}
|
||||
})
|
||||
const getUserInfo = async () => {
|
||||
const users = await getUserProfileApi()
|
||||
userInfo.user = users
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getUserInfo()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<UserAvatarVue :img="userInfo.user.avatar" />
|
||||
</div>
|
||||
<ul class="list-group list-group-striped">
|
||||
<li class="list-group-item">
|
||||
<Icon icon="ep:user" class="mr-5px" />{{ t('profile.user.username') }}
|
||||
<div class="pull-right">{{ userInfo.user.username }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon icon="ep:phone" class="mr-5px" />{{ t('profile.user.mobile') }}
|
||||
<div class="pull-right">{{ userInfo.user.mobile }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon icon="fontisto:email" class="mr-5px" />{{ t('profile.user.email') }}
|
||||
<div class="pull-right">{{ userInfo.user.email }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon icon="carbon:tree-view-alt" class="mr-5px" />{{ t('profile.user.dept') }}
|
||||
<div class="pull-right" v-if="userInfo.user.dept">{{ userInfo.user.dept.name }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon icon="ep:suitcase" class="mr-5px" />{{ t('profile.user.posts') }}
|
||||
<div class="pull-right" v-if="userInfo.user.posts">
|
||||
{{ userInfo.user.posts.map((post) => post.name).join(',') }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon icon="icon-park-outline:peoples" class="mr-5px" />{{ t('profile.user.roles') }}
|
||||
<div class="pull-right" v-if="userInfo.user.roles">
|
||||
{{ userInfo.user.roles.map((role) => role.name).join(',') }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon icon="ep:calendar" class="mr-5px" />{{ t('profile.user.createTime') }}
|
||||
<div class="pull-right">{{ dayjs(userInfo.user.createTime).format('YYYY-MM-DD') }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.text-center {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
height: 120px;
|
||||
}
|
||||
.list-group-striped > .list-group-item {
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-radius: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
padding-left: 0px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
border-bottom: 1px solid #e7eaec;
|
||||
border-top: 1px solid #e7eaec;
|
||||
margin-bottom: -1px;
|
||||
padding: 11px 0px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.pull-right {
|
||||
float: right !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import { InputPassword } from '@/components/InputPassword'
|
||||
import { ElForm, ElFormItem, ElMessage } from 'element-plus'
|
||||
import type { FormRules, FormInstance } from 'element-plus'
|
||||
import { updateUserPwdApi } from '@/api/system/user/profile'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
const { t } = useI18n()
|
||||
const formRef = ref<FormInstance>()
|
||||
const password = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
// 表单校验
|
||||
const equalToPassword = (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: 3, max: 5, 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 updateUserPwdApi(password.oldPassword, password.newPassword)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
})
|
||||
}
|
||||
const reset = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<el-form ref="formRef" :model="password" :rules="rules" label-width="80px">
|
||||
<el-form-item :label="t('profile.password.oldPassword')">
|
||||
<InputPassword v-model="password.oldPassword" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('profile.password.newPassword')">
|
||||
<InputPassword v-model="password.newPassword" strength />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('profile.password.confirmPassword')">
|
||||
<InputPassword v-model="password.confirmPassword" strength />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit(formRef)">{{ t('common.save') }}</el-button>
|
||||
<el-button type="danger" @click="reset(formRef)">{{ t('common.reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
202
yudao-ui-admin-vue3/src/views/Profile/components/UserAvatar.vue
Normal file
202
yudao-ui-admin-vue3/src/views/Profile/components/UserAvatar.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import 'vue-cropper/dist/index.css'
|
||||
import { VueCropper } from 'vue-cropper'
|
||||
import { ElRow, ElCol, ElUpload, ElMessage } from 'element-plus'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { uploadAvatarApi } from '@/api/system/user/profile'
|
||||
const cropper = ref()
|
||||
const props = defineProps({
|
||||
img: propTypes.string.def('')
|
||||
})
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
cropperVisible: false,
|
||||
dialogTitle: '编辑头像',
|
||||
options: {
|
||||
img: props.img, //裁剪图片的地址
|
||||
autoCrop: true, // 是否默认生成截图框
|
||||
autoCropWidth: 200, // 默认生成截图框宽度
|
||||
autoCropHeight: 200, // 默认生成截图框高度
|
||||
fixedBox: true // 固定截图框大小 不允许改变
|
||||
},
|
||||
previews: {
|
||||
img: '',
|
||||
url: ''
|
||||
}
|
||||
})
|
||||
/** 编辑头像 */
|
||||
const editCropper = () => {
|
||||
state.dialogVisible = true
|
||||
state.cropperVisible = true
|
||||
}
|
||||
/** 覆盖默认上传行为 */
|
||||
const requestUpload = () => {}
|
||||
/** 向左旋转 */
|
||||
const rotateLeft = () => {
|
||||
cropper.value.rotateLeft()
|
||||
}
|
||||
/** 向右旋转 */
|
||||
const rotateRight = () => {
|
||||
cropper.value.rotateRight()
|
||||
}
|
||||
/** 图片缩放 */
|
||||
const changeScale = (num: number) => {
|
||||
num = num || 1
|
||||
cropper.value.changeScale(num)
|
||||
}
|
||||
/** 上传预处理 */
|
||||
const beforeUpload = (file: Blob) => {
|
||||
if (file.type.indexOf('image/') == -1) {
|
||||
ElMessage('文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。')
|
||||
} else {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = () => {
|
||||
state.options.img = reader.result
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 上传图片 */
|
||||
const uploadImg = () => {
|
||||
cropper.value.getCropBlob((data) => {
|
||||
let formData = new FormData()
|
||||
formData.append('avatarfile', data)
|
||||
uploadAvatarApi(formData)
|
||||
})
|
||||
}
|
||||
/** 实时预览 */
|
||||
const realTime = (data) => {
|
||||
state.previews = data
|
||||
}
|
||||
watch(
|
||||
() => props.img,
|
||||
() => {
|
||||
if (props.img) {
|
||||
state.options.img = props.img
|
||||
state.previews.img = props.img
|
||||
state.previews.url = props.img
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<div class="user-info-head" @click="editCropper()">
|
||||
<img :src="state.options.img" title="点击上传头像" class="img-circle img-lg" alt="" />
|
||||
</div>
|
||||
<Dialog
|
||||
v-model="state.dialogVisible"
|
||||
:title="state.dialogTitle"
|
||||
width="50%"
|
||||
:maxHeight="350"
|
||||
style="padding: 30px 20px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
|
||||
<VueCropper
|
||||
ref="cropper"
|
||||
:img="state.options.img"
|
||||
:info="true"
|
||||
:autoCrop="state.options.autoCrop"
|
||||
:autoCropWidth="state.options.autoCropWidth"
|
||||
:autoCropHeight="state.options.autoCropHeight"
|
||||
:fixedBox="state.options.fixedBox"
|
||||
@real-time="realTime"
|
||||
v-if="state.cropperVisible"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
|
||||
<div class="avatar-upload-preview">
|
||||
<img
|
||||
:src="state.previews.url"
|
||||
:style="state.previews.img"
|
||||
style="!max-width: 100%"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<el-row>
|
||||
<el-col :lg="2" :md="2">
|
||||
<el-upload
|
||||
action="#"
|
||||
:http-request="requestUpload"
|
||||
:show-file-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<el-button size="small">
|
||||
<Icon icon="ep:upload-filled" class="mr-5px" />
|
||||
选择
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</el-col>
|
||||
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
|
||||
<el-button size="small" @click="changeScale(1)">
|
||||
<Icon icon="ep:zoom-in" class="mr-5px" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
|
||||
<el-button size="small" @click="changeScale(-1)">
|
||||
<Icon icon="ep:zoom-out" class="mr-5px" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
|
||||
<el-button size="small" @click="rotateLeft()">
|
||||
<Icon icon="ep:arrow-left-bold" class="mr-5px" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
|
||||
<el-button size="small" @click="rotateRight()">
|
||||
<Icon icon="ep:arrow-right-bold" class="mr-5px" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
|
||||
<el-button size="small" type="primary" @click="uploadImg()">提 交</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<style scoped>
|
||||
.user-info-head {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.img-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.img-lg {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
.avatar-upload-preview {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(50%, -50%);
|
||||
transform: translate(50%, -50%);
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
-webkit-box-shadow: 0 0 4px #ccc;
|
||||
box-shadow: 0 0 4px #ccc;
|
||||
overflow: hidden;
|
||||
}
|
||||
.user-info-head:hover:after {
|
||||
content: '+';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
color: #eee;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
cursor: pointer;
|
||||
line-height: 110px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import { ElTable, ElTableColumn } from 'element-plus'
|
||||
import { onMounted, reactive } from 'vue'
|
||||
interface sociaType {
|
||||
title: string
|
||||
type: string
|
||||
source: string
|
||||
img: string
|
||||
}
|
||||
interface socialUserType {
|
||||
socialUser: {
|
||||
socia: sociaType[]
|
||||
}
|
||||
}
|
||||
const state = reactive<socialUserType>({
|
||||
socialUser: {
|
||||
socia: []
|
||||
}
|
||||
})
|
||||
const initSocial = () => {
|
||||
console.info(1)
|
||||
}
|
||||
const bind = () => {
|
||||
console.info(1)
|
||||
}
|
||||
const unbind = () => {
|
||||
console.info(1)
|
||||
}
|
||||
onMounted(async () => {
|
||||
await initSocial()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<el-table :data="state.socialUser.socia" :show-header="false">
|
||||
<el-table-column label="社交平台" align="left" width="120" prop="socia">
|
||||
<template #socia="{ row }">
|
||||
<img style="height: 20px; vertical-align: middle" :src="row.img" alt="" />
|
||||
{{ row.title }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="left" prop="action">
|
||||
<template #action="{ row }">
|
||||
<div v-if="row.openid">
|
||||
已绑定
|
||||
<el-button link type="primary" @click="unbind()">(解绑)</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
未绑定
|
||||
<el-button link type="primary" @click="bind()">(绑定)</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
@@ -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 }
|
||||
30
yudao-ui-admin-vue3/src/views/Redirect/Redirect.vue
Normal file
30
yudao-ui-admin-vue3/src/views/Redirect/Redirect.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { unref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
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>
|
||||
7
yudao-ui-admin-vue3/src/views/bpm/definition/index.vue
Normal file
7
yudao-ui-admin-vue3/src/views/bpm/definition/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
7
yudao-ui-admin-vue3/src/views/bpm/form/index.vue
Normal file
7
yudao-ui-admin-vue3/src/views/bpm/form/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
7
yudao-ui-admin-vue3/src/views/bpm/group/index.vue
Normal file
7
yudao-ui-admin-vue3/src/views/bpm/group/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
7
yudao-ui-admin-vue3/src/views/bpm/model/index.vue
Normal file
7
yudao-ui-admin-vue3/src/views/bpm/model/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
7
yudao-ui-admin-vue3/src/views/bpm/oa/index.vue
Normal file
7
yudao-ui-admin-vue3/src/views/bpm/oa/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
7
yudao-ui-admin-vue3/src/views/bpm/task/done/index.vue
Normal file
7
yudao-ui-admin-vue3/src/views/bpm/task/done/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
7
yudao-ui-admin-vue3/src/views/bpm/task/my/index.vue
Normal file
7
yudao-ui-admin-vue3/src/views/bpm/task/my/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
7
yudao-ui-admin-vue3/src/views/bpm/task/todo/index.vue
Normal file
7
yudao-ui-admin-vue3/src/views/bpm/task/todo/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,78 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index'
|
||||
},
|
||||
{
|
||||
label: '链路追踪',
|
||||
field: 'traceId'
|
||||
},
|
||||
{
|
||||
label: '用户编号',
|
||||
field: 'userId',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '用户类型',
|
||||
field: 'userType',
|
||||
dictType: DICT_TYPE.USER_TYPE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '应用名',
|
||||
field: 'applicationName',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '请求方法名',
|
||||
field: 'requestMethod'
|
||||
},
|
||||
{
|
||||
label: '请求地址',
|
||||
field: 'requestUrl',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '请求时间',
|
||||
field: 'beginTime'
|
||||
},
|
||||
{
|
||||
label: '执行时长',
|
||||
field: 'duration'
|
||||
},
|
||||
{
|
||||
label: '操作结果',
|
||||
field: 'resultCode',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '300px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
99
yudao-ui-admin-vue3/src/views/infra/apiAccessLog/index.vue
Normal file
99
yudao-ui-admin-vue3/src/views/infra/apiAccessLog/index.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { ApiAccessLogVO } from '@/api/infra/apiAccessLog/types'
|
||||
import { allSchemas } from './apiAccessLog.data'
|
||||
import * as ApiAccessLogApi from '@/api/infra/apiAccessLog'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<ApiAccessLogVO>, ApiAccessLogVO>({
|
||||
getListApi: ApiAccessLogApi.getApiAccessLogPageApi
|
||||
})
|
||||
const { getList, setSearchParams } = methods
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('') // 弹出层标题
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = (row: ApiAccessLogVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
dialogTitle.value = t('action.detail')
|
||||
dialogVisible.value = true
|
||||
}
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #userType="{ row }">
|
||||
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
|
||||
</template>
|
||||
<template #beginTime="{ row }">
|
||||
<span>{{ dayjs(row.beginTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #duration="{ row }">
|
||||
<span>{{ row.duration + 'ms' }}</span>
|
||||
</template>
|
||||
<template #resultCode="{ row }">
|
||||
<span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:api-access-log:query']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
|
||||
<template #userType="{ row }">
|
||||
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
|
||||
</template>
|
||||
<template #beginTime="{ row }">
|
||||
<span>{{ dayjs(row.beginTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #duration="{ row }">
|
||||
<span>{{ row.duration + 'ms' }}</span>
|
||||
</template>
|
||||
<template #resultCode="{ row }">
|
||||
<span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,87 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index'
|
||||
},
|
||||
{
|
||||
label: '链路追踪',
|
||||
field: 'traceId'
|
||||
},
|
||||
{
|
||||
label: '用户编号',
|
||||
field: 'userId',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '用户类型',
|
||||
field: 'userType',
|
||||
dictType: DICT_TYPE.USER_TYPE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '应用名',
|
||||
field: 'applicationName',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '请求方法名',
|
||||
field: 'requestMethod'
|
||||
},
|
||||
{
|
||||
label: '请求地址',
|
||||
field: 'requestUrl',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '异常发生时间',
|
||||
field: 'exceptionTime'
|
||||
},
|
||||
{
|
||||
label: '异常名',
|
||||
field: 'exceptionName'
|
||||
},
|
||||
{
|
||||
label: '处理状态',
|
||||
field: 'processStatus',
|
||||
dictType: DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '处理人',
|
||||
field: 'processUserId'
|
||||
},
|
||||
{
|
||||
label: '处理时间',
|
||||
field: 'processTime'
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '300px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
141
yudao-ui-admin-vue3/src/views/infra/apiErrorLog/index.vue
Normal file
141
yudao-ui-admin-vue3/src/views/infra/apiErrorLog/index.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { ApiErrorLogVO } from '@/api/infra/apiErrorLog/types'
|
||||
import { allSchemas } from './apiErrorLog.data'
|
||||
import * as ApiErrorLogApi from '@/api/infra/apiErrorLog'
|
||||
import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<ApiErrorLogVO>, ApiErrorLogVO>({
|
||||
getListApi: ApiErrorLogApi.getApiErrorLogPageApi,
|
||||
exportListApi: ApiErrorLogApi.exportApiErrorLogApi
|
||||
})
|
||||
const { getList, setSearchParams, exportList } = methods
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('') // 弹出层标题
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('用户数据.xls')
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = (row: ApiErrorLogVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
dialogTitle.value = t('action.detail')
|
||||
dialogVisible.value = true
|
||||
}
|
||||
// 异常处理操作
|
||||
const handleProcessClick = (row: ApiErrorLogVO, processSttatus: number, type: string) => {
|
||||
ElMessageBox.confirm('确认标记为' + type + '?', t('common.reminder'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
ApiErrorLogApi.updateApiErrorLogPageApi(row.id, processSttatus).then(() => {
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
getList()
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<el-button v-hasPermi="['infra:api-error-log:export']" @click="handleExport">
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #userType="{ row }">
|
||||
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
|
||||
</template>
|
||||
<template #processStatus="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" :value="row.processStatus" />
|
||||
</template>
|
||||
<template #exceptionTime="{ row }">
|
||||
<span>{{ dayjs(row.exceptionTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #processTime="{ row }">
|
||||
<span v-if="row.processTime">{{
|
||||
dayjs(row.processTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:api-error-log:export']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
|
||||
v-hasPermi="['infra:api-error-log:update-status']"
|
||||
@click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.DONE, '已处理')"
|
||||
>
|
||||
<Icon icon="ep:cpu" class="mr-5px" /> 已处理
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
|
||||
v-hasPermi="['infra:api-error-log:update-status']"
|
||||
@click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.IGNORE, '已忽略')"
|
||||
>
|
||||
<Icon icon="ep:mute-notification" class="mr-5px" /> 已忽略
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
|
||||
<template #userType="{ row }">
|
||||
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
|
||||
</template>
|
||||
<template #processStatus="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" :value="row.processStatus" />
|
||||
</template>
|
||||
<template #exceptionTime="{ row }">
|
||||
<span>{{ dayjs(row.exceptionTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
7
yudao-ui-admin-vue3/src/views/infra/build/index.vue
Normal file
7
yudao-ui-admin-vue3/src/views/infra/build/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
67
yudao-ui-admin-vue3/src/views/infra/codegen/EditTable.vue
Normal file
67
yudao-ui-admin-vue3/src/views/infra/codegen/EditTable.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref, onMounted } from 'vue'
|
||||
import { ContentDetailWrap } from '@/components/ContentDetailWrap'
|
||||
import BasicInfoForm from './components/BasicInfoForm.vue'
|
||||
import CloumInfoFormVue from './components/CloumInfoForm.vue'
|
||||
import GenInfoFormVue from './components/GenInfoForm.vue'
|
||||
import { ElTabs, ElTabPane, ElButton } from 'element-plus'
|
||||
import { getCodegenTableApi } from '@/api/infra/codegen'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CodegenColumnVO, CodegenTableVO } from '@/api/infra/codegen/types'
|
||||
const { t } = useI18n()
|
||||
const { push } = useRouter()
|
||||
const { query } = useRoute()
|
||||
const tableCurrentRow = ref<Nullable<CodegenTableVO>>(null)
|
||||
const cloumCurrentRow = ref<CodegenColumnVO[]>()
|
||||
const getList = async () => {
|
||||
const id = query.id as unknown as number
|
||||
if (id) {
|
||||
// 获取表详细信息
|
||||
const res = await getCodegenTableApi(id)
|
||||
tableCurrentRow.value = res.table
|
||||
cloumCurrentRow.value = res.columns
|
||||
}
|
||||
}
|
||||
const loading = ref(false)
|
||||
const activeName = ref('cloum')
|
||||
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>()
|
||||
const genInfoRef = ref<ComponentRef<typeof GenInfoFormVue>>()
|
||||
// TODO: 提交
|
||||
const submitForm = async () => {
|
||||
const basicInfo = unref(basicInfoRef)
|
||||
const genInfo = unref(genInfoRef)
|
||||
const basicValidate = await basicInfo?.elFormRef?.validate()?.catch(() => {})
|
||||
const genValidate = await genInfo?.elFormRef?.validate()?.catch(() => {})
|
||||
if (basicValidate && genValidate) {
|
||||
const basicInfoData = (await basicInfo?.getFormData()) as CodegenTableVO
|
||||
const genInfoData = (await genInfo?.getFormData()) as CodegenTableVO
|
||||
console.info(basicInfoData)
|
||||
console.info(genInfoData)
|
||||
}
|
||||
console.info(1)
|
||||
}
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<ContentDetailWrap title="代码生成" @back="push('/infra/codegen')">
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
<BasicInfoForm ref="basicInfoRef" :current-row="tableCurrentRow" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="字段信息" name="cloum">
|
||||
<CloumInfoFormVue ref="cloumInfoRef" :current-row="cloumCurrentRow" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="生成信息" name="genInfo">
|
||||
<GenInfoFormVue ref="basicInfoRef" :current-row="tableCurrentRow" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #right>
|
||||
<el-button type="primary" :loading="loading" @click="submitForm">
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</ContentDetailWrap>
|
||||
</template>
|
||||
74
yudao-ui-admin-vue3/src/views/infra/codegen/codegen.data.ts
Normal file
74
yudao-ui-admin-vue3/src/views/infra/codegen/codegen.data.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
title: [required],
|
||||
type: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '表名称',
|
||||
field: 'tableName',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '表描述',
|
||||
field: 'tableComment',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '实体',
|
||||
field: 'className',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.updateTime'),
|
||||
field: 'updateTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '500px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType, reactive, watch } from 'vue'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CodegenTableVO } from '@/api/infra/codegen/types'
|
||||
import { Form } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
const props = defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<Nullable<CodegenTableVO>>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
const rules = reactive({
|
||||
tableName: [required],
|
||||
tableComment: [required],
|
||||
className: [required],
|
||||
author: [required]
|
||||
})
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
label: '表名称',
|
||||
field: 'tableName',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '表描述',
|
||||
field: 'tableComment',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '实体类名称',
|
||||
field: 'className',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '作者',
|
||||
field: 'author',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
field: 'remark',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
}
|
||||
])
|
||||
const { register, methods, elFormRef } = useForm({
|
||||
schema
|
||||
})
|
||||
watch(
|
||||
() => props.currentRow,
|
||||
(currentRow) => {
|
||||
if (!currentRow) return
|
||||
const { setValues } = methods
|
||||
setValues(currentRow)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
elFormRef,
|
||||
getFormData: methods.getFormData
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<Form :rules="rules" @register="register" />
|
||||
</template>
|
||||
@@ -0,0 +1,137 @@
|
||||
<script setup lang="ts">
|
||||
import { ElTable, ElTableColumn, ElInput, ElSelect, ElOption, ElCheckbox } from 'element-plus'
|
||||
import { onMounted, PropType, ref } from 'vue'
|
||||
import { CodegenColumnVO } from '@/api/infra/codegen/types'
|
||||
import { listSimpleDictTypeApi } from '@/api/system/dict/dict.type'
|
||||
import { DictTypeVO } from '@/api/system/dict/types'
|
||||
defineProps({
|
||||
currentRow: {
|
||||
type: Array as unknown as PropType<CodegenColumnVO[]>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
/** 查询字典下拉列表 */
|
||||
const dictOptions = ref<DictTypeVO[]>()
|
||||
const getDictOptions = async () => {
|
||||
const res = await listSimpleDictTypeApi()
|
||||
dictOptions.value = res
|
||||
}
|
||||
const tableHeight = document.documentElement.scrollHeight - 245 + 'px'
|
||||
onMounted(async () => {
|
||||
await getDictOptions()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<el-table ref="dragTable" :data="currentRow" row-key="columnId" :max-height="tableHeight">
|
||||
<el-table-column
|
||||
label="字段列名"
|
||||
prop="columnName"
|
||||
min-width="10%"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column label="字段描述" min-width="10%">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.columnComment" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="物理类型"
|
||||
prop="dataType"
|
||||
min-width="10%"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<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="Date" value="Date" />
|
||||
<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 true-label="true" false-label="false" v-model="scope.row.createOperation" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编辑" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox true-label="true" false-label="false" v-model="scope.row.updateOperation" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="列表" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox
|
||||
true-label="true"
|
||||
false-label="false"
|
||||
v-model="scope.row.listOperationResult"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperation" />
|
||||
</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 true-label="true" false-label="false" v-model="scope.row.nullable" />
|
||||
</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>
|
||||
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType, reactive, watch } from 'vue'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CodegenTableVO } from '@/api/infra/codegen/types'
|
||||
import { Form } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
|
||||
const props = defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<Nullable<CodegenTableVO>>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
const rules = reactive({
|
||||
templateType: [required],
|
||||
scene: [required],
|
||||
moduleName: [required],
|
||||
businessName: [required],
|
||||
businessPackage: [required],
|
||||
className: [required],
|
||||
classComment: [required]
|
||||
})
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
label: '生成模板',
|
||||
field: 'templateType',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)
|
||||
},
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '生成场景',
|
||||
field: 'scene',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)
|
||||
},
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '模块名',
|
||||
field: 'moduleName',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '业务名',
|
||||
field: 'businessName',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '类名称',
|
||||
field: 'className',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '类描述',
|
||||
field: 'classComment',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '上级菜单',
|
||||
field: 'parentMenuId',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
}
|
||||
])
|
||||
const { register, methods, elFormRef } = useForm({
|
||||
schema
|
||||
})
|
||||
watch(
|
||||
() => props.currentRow,
|
||||
(currentRow) => {
|
||||
if (!currentRow) return
|
||||
const { setValues } = methods
|
||||
setValues(currentRow)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
elFormRef,
|
||||
getFormData: methods.getFormData
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<Form :rules="rules" @register="register" />
|
||||
</template>
|
||||
@@ -0,0 +1,128 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { getSchemaTableListApi, createCodegenListApi } from '@/api/infra/codegen'
|
||||
import {
|
||||
ElMessage,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElEmpty
|
||||
} from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
import { getDataSourceConfigListApi } from '@/api/infra/dataSourceConfig'
|
||||
import type { DataSourceConfigVO } from '@/api/infra/dataSourceConfig/types'
|
||||
import type { DatabaseTableVO } from '@/api/infra/codegen/types'
|
||||
const { t } = useI18n() // 国际化
|
||||
const { emitter } = useEmitt()
|
||||
// ======== 显示页面 ========
|
||||
const visible = ref(false)
|
||||
const queryParams = reactive({
|
||||
tableName: undefined,
|
||||
tableComment: undefined,
|
||||
dataSourceConfigId: 0
|
||||
})
|
||||
const dataSourceConfigs = ref<DataSourceConfigVO[]>([])
|
||||
const show = async () => {
|
||||
const res = await getDataSourceConfigListApi()
|
||||
dataSourceConfigs.value = res
|
||||
queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id
|
||||
visible.value = true
|
||||
await getList()
|
||||
}
|
||||
/** 查询表数据 */
|
||||
const dbTableList = ref<DatabaseTableVO[]>([])
|
||||
|
||||
/** 查询表数据 */
|
||||
const getList = async () => {
|
||||
const res = await getSchemaTableListApi(queryParams)
|
||||
dbTableList.value = res
|
||||
}
|
||||
// 查询操作
|
||||
const handleQuery = async () => {
|
||||
await getList()
|
||||
}
|
||||
// 重置操作
|
||||
const resetQuery = async () => {
|
||||
queryParams.tableName = undefined
|
||||
queryParams.tableComment = undefined
|
||||
await getList()
|
||||
}
|
||||
/** 多选框选中数据 */
|
||||
const tables = ref<string[]>([])
|
||||
const handleSelectionChange = (val: DatabaseTableVO[]) => {
|
||||
tables.value = val.map((item) => item.name)
|
||||
}
|
||||
/** 导入按钮操作 */
|
||||
const handleImportTable = () => {
|
||||
if (tables.value.length === 0) {
|
||||
ElMessage.error('请选择要导入的表')
|
||||
return
|
||||
}
|
||||
createCodegenListApi({
|
||||
dataSourceConfigId: queryParams.dataSourceConfigId,
|
||||
tableNames: tables.value
|
||||
}).then((res) => {
|
||||
ElMessage.success(res.msg)
|
||||
visible.value = false
|
||||
emitter.emit('ok')
|
||||
})
|
||||
}
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<!-- 导入表 -->
|
||||
<Dialog title="导入表" v-model="visible" maxHeight="500px" width="50%">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true">
|
||||
<!-- <el-form-item label="数据源" prop="dataSourceConfigId">
|
||||
<el-select v-model="queryParams.dataSourceConfigId" placeholder="请选择数据源" clearable>
|
||||
<el-option
|
||||
v-for="config in dataSourceConfigs"
|
||||
:key="config.id"
|
||||
:label="config.name"
|
||||
:value="config.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px" />
|
||||
{{ t('common.query') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh-right" class="mr-5px" />
|
||||
{{ t('common.reset') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
ref="table"
|
||||
:data="dbTableList"
|
||||
@selection-change="handleSelectionChange"
|
||||
height="400px"
|
||||
>
|
||||
<template #empty>
|
||||
<el-empty description="加载中" />
|
||||
</template>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="name" label="表名称" :show-overflow-tooltip="true" />
|
||||
<el-table-column prop="comment" label="表描述" :show-overflow-tooltip="true" />
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="handleImportTable">{{ t('action.import') }}</el-button>
|
||||
<el-button @click="visible = false">{{ t('dialog.close') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,148 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, unref } from 'vue'
|
||||
import { handleTree2 } from '@/utils/tree'
|
||||
import { ElCard, ElTree, ElTabs, ElTabPane, ElMessage } from 'element-plus'
|
||||
import { previewCodegenApi } from '@/api/infra/codegen'
|
||||
import { CodegenTableVO, CodegenPreviewVO } from '@/api/infra/codegen/types'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
const { t } = useI18n()
|
||||
// ======== 显示页面 ========
|
||||
const preview = reactive({
|
||||
open: false,
|
||||
titel: '代码预览',
|
||||
fileTree: [],
|
||||
activeName: ''
|
||||
})
|
||||
const previewCodegen = ref<CodegenPreviewVO[]>()
|
||||
const show = async (row: CodegenTableVO) => {
|
||||
const res = await previewCodegenApi(row.id)
|
||||
let file = handleFiles(res)
|
||||
previewCodegen.value = res
|
||||
preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/')
|
||||
preview.activeName = res[0].filePath
|
||||
preview.open = true
|
||||
}
|
||||
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 handleFiles = (datas: 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) {
|
||||
ElMessage.error(t('common.copyError'))
|
||||
} else {
|
||||
await copy()
|
||||
if (unref(copied)) {
|
||||
ElMessage.success(t('common.copySuccess'))
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<Dialog title="预览" v-model="preview.open" top="5vh" maxHeight="800px" width="90%">
|
||||
<div class="flex">
|
||||
<el-card class="w-1/4" :gutter="12" shadow="hover">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="id"
|
||||
:data="preview.fileTree"
|
||||
:expand-on-click-node="false"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</el-card>
|
||||
<el-card class="w-3/4" style="margin-left: 10px" :gutter="12" shadow="hover">
|
||||
<el-tabs v-model="preview.activeName">
|
||||
<el-tab-pane
|
||||
v-for="item in previewCodegen"
|
||||
:label="item.filePath.substring(item.filePath.lastIndexOf('/') + 1)"
|
||||
:name="item.filePath"
|
||||
:key="item.filePath"
|
||||
>
|
||||
<el-button text style="float: right" @click="copy(item.code)">
|
||||
{{ t('common.copy') }}
|
||||
</el-button>
|
||||
<pre>{{ item.code }}</pre>
|
||||
<!-- <pre><code class="language-html" v-html="highlightedCode(item)"></code></pre> -->
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
142
yudao-ui-admin-vue3/src/views/infra/codegen/index.vue
Normal file
142
yudao-ui-admin-vue3/src/views/infra/codegen/index.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { CodegenTableVO } from '@/api/infra/codegen/types'
|
||||
import { allSchemas } from './codegen.data'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import ImportTable from './components/ImportTable.vue'
|
||||
import Preview from './components/Preview.vue'
|
||||
import download from '@/utils/download'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter()
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<CodegenTableVO>, CodegenTableVO>({
|
||||
getListApi: CodegenApi.getCodegenTablePageApi,
|
||||
delListApi: CodegenApi.deleteCodegenTableApi
|
||||
})
|
||||
const { getList, setSearchParams, delList } = methods
|
||||
// 导入操作
|
||||
const importRef = ref()
|
||||
const openImportTable = () => {
|
||||
importRef.value.show()
|
||||
}
|
||||
// 预览操作
|
||||
const previewRef = ref()
|
||||
const handlePreview = (row: CodegenTableVO) => {
|
||||
previewRef.value.show(row)
|
||||
}
|
||||
// 编辑操作
|
||||
const handleEditTable = (row: CodegenTableVO) => {
|
||||
push('/codegen/edit?id=' + row.id)
|
||||
}
|
||||
// 同步操作
|
||||
const handleSynchDb = (row: CodegenTableVO) => {
|
||||
// 基于 DB 同步
|
||||
const tableName = row.tableName
|
||||
ElMessageBox.confirm('确认要强制同步' + tableName + '表结构吗?', t('common.reminder'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
await CodegenApi.syncCodegenFromDBApi(row.id)
|
||||
ElMessage.success('同步成功')
|
||||
})
|
||||
}
|
||||
// 生成代码操作
|
||||
const handleGenTable = (row: CodegenTableVO) => {
|
||||
const res = CodegenApi.downloadCodegenApi(row.id)
|
||||
download.zip(res, 'codegen-' + row.className + '.zip')
|
||||
}
|
||||
// 删除操作
|
||||
const handleDelete = (row: CodegenTableVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
// 查询操作
|
||||
const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['infra:codegen:create']" @click="openImportTable">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.import') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #updateTime="{ row }">
|
||||
<span>{{ dayjs(row.updateTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:codegen:preview']"
|
||||
@click="handlePreview(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.preview') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:codegen:update']"
|
||||
@click="handleEditTable(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:codegen:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:codegen:update']"
|
||||
@click="handleSynchDb(row)"
|
||||
>
|
||||
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('action.sync') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:codegen:download']"
|
||||
@click="handleGenTable(row)"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.generate') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
<ImportTable ref="importRef" @ok="handleQuery" />
|
||||
<Preview ref="previewRef" />
|
||||
</template>
|
||||
108
yudao-ui-admin-vue3/src/views/infra/config/config.data.ts
Normal file
108
yudao-ui-admin-vue3/src/views/infra/config/config.data.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
category: [required],
|
||||
name: [required],
|
||||
key: [required],
|
||||
value: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '参数分类',
|
||||
field: 'category'
|
||||
},
|
||||
{
|
||||
label: '参数名称',
|
||||
field: 'name',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '参数键名',
|
||||
field: 'key',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '参数键值',
|
||||
field: 'value'
|
||||
},
|
||||
{
|
||||
label: '系统内置',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.INFRA_CONFIG_TYPE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '是否可见',
|
||||
field: 'visible',
|
||||
form: {
|
||||
component: 'RadioButton',
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('form.remark'),
|
||||
field: 'remark',
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
204
yudao-ui-admin-vue3/src/views/infra/config/index.vue
Normal file
204
yudao-ui-admin-vue3/src/views/infra/config/index.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { ConfigVO } from '@/api/infra/config/types'
|
||||
import { rules, allSchemas } from './config.data'
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<ConfigVO>, ConfigVO>({
|
||||
getListApi: ConfigApi.getConfigPageApi,
|
||||
delListApi: ConfigApi.deleteConfigApi,
|
||||
exportListApi: ConfigApi.exportConfigApi
|
||||
})
|
||||
const { getList, setSearchParams, delList, exportList } = methods
|
||||
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('参数配置.xls')
|
||||
}
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: ConfigVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await ConfigApi.getConfigApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as ConfigVO
|
||||
if (actionType.value === 'create') {
|
||||
await ConfigApi.createConfigApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ConfigApi.updateConfigApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: ConfigVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: ConfigVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['infra:config:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
v-hasPermi="['infra:config:export']"
|
||||
:loading="tableObject.exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #visible="{ row }">
|
||||
<span>{{ row.visible ? '是' : '否' }} </span>
|
||||
</template>
|
||||
<template #type="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_CONFIG_TYPE" :value="row.type" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:config:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:config:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:config:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #visible="{ row }">
|
||||
<span>{{ row.visible ? '是' : '否' }} </span>
|
||||
</template>
|
||||
<template #type="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_CONFIG_TYPE" :value="row.type" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,75 @@
|
||||
import { reactive } from 'vue'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
url: [required],
|
||||
username: [required],
|
||||
password: [required]
|
||||
})
|
||||
// 新增 + 修改
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '数据源名称',
|
||||
field: 'name'
|
||||
},
|
||||
{
|
||||
label: '数据源连接',
|
||||
field: 'url',
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '用户名',
|
||||
field: 'username'
|
||||
},
|
||||
{
|
||||
label: '密码',
|
||||
field: 'password',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
label: t('table.action'),
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
161
yudao-ui-admin-vue3/src/views/infra/dataSourceConfig/index.vue
Normal file
161
yudao-ui-admin-vue3/src/views/infra/dataSourceConfig/index.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import { rules, allSchemas } from './dataSourceConfig.data'
|
||||
import type { DataSourceConfigVO } from '@/api/infra/dataSourceConfig/types'
|
||||
import * as DataSourceConfiggApi from '@/api/infra/dataSourceConfig'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
const { t } = useI18n() // 国际化
|
||||
const tableData = ref()
|
||||
const getList = async () => {
|
||||
const res = await DataSourceConfiggApi.getDataSourceConfigListApi()
|
||||
tableData.value = res
|
||||
}
|
||||
// ========== CRUD 相关 ==========
|
||||
const loading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: DataSourceConfigVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await DataSourceConfiggApi.getDataSourceConfigApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
loading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as DataSourceConfigVO
|
||||
if (actionType.value === 'create') {
|
||||
await DataSourceConfiggApi.createDataSourceConfigApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await DataSourceConfiggApi.updateDataSourceConfigApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (row: DataSourceConfigVO) => {
|
||||
await DataSourceConfiggApi.deleteDataSourceConfigApi(row.id)
|
||||
ElMessage.success(t('common.delSuccess'))
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: DataSourceConfigVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button
|
||||
v-hasPermi="['infra:data-source-config:create']"
|
||||
type="primary"
|
||||
@click="handleCreate"
|
||||
>
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<Table :columns="allSchemas.tableColumns" :data="tableData">
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ row.createTime ? dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') : '' }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:data-source-config:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:data-source-config:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:data-source-config:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ row.createTime ? dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') : '' }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
55
yudao-ui-admin-vue3/src/views/infra/dbDoc/index.vue
Normal file
55
yudao-ui-admin-vue3/src/views/infra/dbDoc/index.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import { IFrame } from '@/components/IFrame'
|
||||
import * as DbDocApi from '@/api/infra/dbDoc'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import download from '@/utils/download'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const loding = ref(true)
|
||||
const src = ref('')
|
||||
/** 页面加载 */
|
||||
const init = async () => {
|
||||
const res = await DbDocApi.exportHtmlApi()
|
||||
let blob = new Blob([res], { type: 'text/html' })
|
||||
let blobUrl = window.URL.createObjectURL(blob)
|
||||
src.value = blobUrl
|
||||
loding.value = false
|
||||
}
|
||||
/** 处理导出 HTML */
|
||||
const handleExportHtml = async () => {
|
||||
const res = await DbDocApi.exportHtmlApi()
|
||||
download.html(res, '数据库文档.html')
|
||||
}
|
||||
/** 处理导出 Word */
|
||||
const handleExportWord = async () => {
|
||||
const res = await DbDocApi.exportHtmlApi()
|
||||
download.word(res, '数据库文档.doc')
|
||||
}
|
||||
/** 处理导出 Markdown */
|
||||
const handleExportMarkdown = async () => {
|
||||
const res = await DbDocApi.exportHtmlApi()
|
||||
download.markdown(res, '数据库文档.md')
|
||||
}
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="数据库文档" message="https://doc.iocoder.cn/db-doc/">
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" @click="handleExportHtml">
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') + ' HTML' }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleExportWord">
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') + ' Word' }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleExportMarkdown">
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') + ' Markdown' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<IFrame v-if="!loding" v-loading="loding" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
12
yudao-ui-admin-vue3/src/views/infra/druid/index.vue
Normal file
12
yudao-ui-admin-vue3/src/views/infra/druid/index.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { IFrame } from '@/components/IFrame'
|
||||
import { ref } from 'vue'
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const src = ref(BASE_URL + '/druid/index.html')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<IFrame :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
53
yudao-ui-admin-vue3/src/views/infra/file/fileList.data.ts
Normal file
53
yudao-ui-admin-vue3/src/views/infra/file/fileList.data.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '文件名',
|
||||
field: 'path',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'URL',
|
||||
field: 'url'
|
||||
},
|
||||
{
|
||||
label: '文件类型',
|
||||
field: 'type'
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '300px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
192
yudao-ui-admin-vue3/src/views/infra/file/index.vue
Normal file
192
yudao-ui-admin-vue3/src/views/infra/file/index.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage, ElUpload, UploadInstance, UploadRawFile, ElImage } from 'element-plus'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { FileVO } from '@/api/infra/file/types'
|
||||
import { allSchemas } from './fileList.data'
|
||||
import * as FileApi from '@/api/infra/file'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
const { wsCache } = useCache()
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<FileVO>, FileVO>({
|
||||
getListApi: FileApi.getFilePageApi,
|
||||
delListApi: FileApi.deleteFileApi
|
||||
})
|
||||
const { getList, setSearchParams, delList } = methods
|
||||
// ========== 上传相关 ==========
|
||||
const uploadDialogVisible = ref(false)
|
||||
const uploadDialogTitle = ref('上传')
|
||||
const updateSupport = ref(0)
|
||||
const uploadDisabled = ref(false)
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
let updateUrl = import.meta.env.VITE_UPLOAD_URL
|
||||
const uploadHeaders = ref()
|
||||
// 文件上传之前判断
|
||||
const beforeUpload = (file: UploadRawFile) => {
|
||||
const isImg = file.type === 'image/jpeg' || 'image/gif' || 'image/png'
|
||||
const isLt5M = file.size / 1024 / 1024 < 5
|
||||
if (!isImg) ElMessage.error('上传文件只能是 xls / xlsx 格式!')
|
||||
if (!isLt5M) ElMessage.error('上传文件大小不能超过 5MB!')
|
||||
return isImg && isLt5M
|
||||
}
|
||||
// 处理上传的文件发生变化
|
||||
// const handleFileChange = (uploadFile: UploadFile): void => {
|
||||
// uploadRef.value.data.path = uploadFile.name
|
||||
// }
|
||||
// 文件上传
|
||||
const submitFileForm = () => {
|
||||
uploadHeaders.value = {
|
||||
Authorization: 'Bearer ' + wsCache.get('ACCESS_TOKEN'),
|
||||
'tenant-id': wsCache.get('tenantId')
|
||||
}
|
||||
uploadDisabled.value = true
|
||||
uploadRef.value!.submit()
|
||||
}
|
||||
// 文件上传成功
|
||||
const handleFileSuccess = (response: any): void => {
|
||||
if (response.code !== 0) {
|
||||
ElMessage.error(response.msg)
|
||||
return
|
||||
}
|
||||
ElMessage.success('上传成功')
|
||||
getList()
|
||||
uploadDialogVisible.value = false
|
||||
uploadDisabled.value = false
|
||||
}
|
||||
// 文件数超出提示
|
||||
const handleExceed = (): void => {
|
||||
ElMessage.error('最多只能上传一个文件!')
|
||||
}
|
||||
// 上传错误提示
|
||||
const excelUploadError = (): void => {
|
||||
ElMessage.error('导入数据失败,请您重新上传!')
|
||||
}
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('') // 弹出层标题
|
||||
// 删除操作
|
||||
const handleDelete = (row: FileVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
// 详情操作
|
||||
const handleDetail = (row: FileVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
dialogTitle.value = t('action.detail')
|
||||
dialogVisible.value = true
|
||||
}
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<el-button type="primary" @click="uploadDialogVisible = true">
|
||||
<Icon icon="ep:upload" class="mr-5px" /> 上传文件
|
||||
</el-button>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #url="{ row }">
|
||||
<el-image
|
||||
v-if="row.type === 'jpg' || 'png' || 'gif'"
|
||||
style="width: 80px; height: 50px"
|
||||
:src="row.url"
|
||||
:key="row.url"
|
||||
fit="contain"
|
||||
lazy
|
||||
/>
|
||||
<span v-else>{{ row.url }}</span>
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button link type="primary" @click="handleDetail(row)">
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:file:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
|
||||
<template #url="{ row }">
|
||||
<el-image
|
||||
v-if="row.type === 'jpg' || 'png' || 'gif'"
|
||||
style="width: 100px; height: 100px"
|
||||
:src="row.url"
|
||||
:key="row.url"
|
||||
lazy
|
||||
/>
|
||||
<span>{{ row.url }}</span>
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
<Dialog v-model="uploadDialogVisible" :title="uploadDialogTitle" :destroy-on-close="true">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:action="updateUrl + '?updateSupport=' + updateSupport"
|
||||
:headers="uploadHeaders"
|
||||
:drag="true"
|
||||
:limit="1"
|
||||
:multiple="true"
|
||||
:show-file-list="true"
|
||||
:disabled="uploadDisabled"
|
||||
:before-upload="beforeUpload"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="handleFileSuccess"
|
||||
:on-error="excelUploadError"
|
||||
:auto-upload="false"
|
||||
accept=".jpg, .png, .gif"
|
||||
>
|
||||
<Icon icon="ep:upload-filled" />
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">请上传 .jpg, .png, .gif 标准格式文件</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="submitFileForm">
|
||||
<Icon icon="ep:upload-filled" />
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="uploadDialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,93 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
storage: [required],
|
||||
config: {
|
||||
basePath: [required],
|
||||
host: [required],
|
||||
port: [required],
|
||||
username: [required],
|
||||
password: [required],
|
||||
mode: [required],
|
||||
endpoint: [required],
|
||||
bucket: [required],
|
||||
accessKey: [required],
|
||||
accessSecret: [required],
|
||||
domain: [required]
|
||||
}
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '配置名',
|
||||
field: 'name',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '存储器',
|
||||
field: 'storage',
|
||||
dictType: DICT_TYPE.INFRA_FILE_STORAGE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '主配置',
|
||||
field: 'primary',
|
||||
dictType: DICT_TYPE.INFRA_BOOLEAN_STRING
|
||||
},
|
||||
{
|
||||
label: t('form.remark'),
|
||||
field: 'remark',
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '400px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
220
yudao-ui-admin-vue3/src/views/infra/fileConfig/index.vue
Normal file
220
yudao-ui-admin-vue3/src/views/infra/fileConfig/index.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { FileConfigVO } from '@/api/infra/fileConfig/types'
|
||||
import { rules, allSchemas } from './fileConfig.data'
|
||||
import * as FileConfigApi from '@/api/infra/fileConfig'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<FileConfigVO>, FileConfigVO>({
|
||||
getListApi: FileConfigApi.getFileConfigPageApi,
|
||||
delListApi: FileConfigApi.deleteFileConfigApi
|
||||
})
|
||||
const { getList, setSearchParams, delList } = methods
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: FileConfigVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await FileConfigApi.getFileConfigApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 主配置操作
|
||||
const handleMaster = (row: FileConfigVO) => {
|
||||
ElMessageBox.confirm('是否确认修改配置【 ' + row.name + ' 】为主配置?', t('common.reminder'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
await FileConfigApi.updateFileConfigMasterApi(row.id)
|
||||
await getList()
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as FileConfigVO
|
||||
if (actionType.value === 'create') {
|
||||
await FileConfigApi.createFileConfigApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await FileConfigApi.updateFileConfigApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: FileConfigVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: FileConfigVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['infra:file-config:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #storage="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="row.storage" />
|
||||
</template>
|
||||
<template #primary="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.master" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:file-config:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:file-config:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:file-config:update']"
|
||||
@click="handleMaster(row)"
|
||||
>
|
||||
<Icon icon="ep:flag" class="mr-5px" /> 主配置
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:file-config:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:share" class="mr-5px" /> {{ t('action.test') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['infra:file-config:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #storage="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="row.storage" />
|
||||
</template>
|
||||
<template #primary="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.master" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
122
yudao-ui-admin-vue3/src/views/infra/job/JobLog.vue
Normal file
122
yudao-ui-admin-vue3/src/views/infra/job/JobLog.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import DictTag from '@/components/DictTag/src/DictTag.vue'
|
||||
import * as JobLogApi from '@/api/infra/jobLog'
|
||||
import { JobLogVO } from '@/api/infra/jobLog/types'
|
||||
import Icon from '@/components/Icon/src/Icon.vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { allSchemas } from './jobLog.data'
|
||||
const { t } = useI18n() // 国际化
|
||||
const { query } = useRoute()
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<JobLogVO>, JobLogVO>({
|
||||
getListApi: JobLogApi.getJobLogPageApi,
|
||||
exportListApi: JobLogApi.exportJobLogApi
|
||||
})
|
||||
const { getList, setSearchParams, exportList } = methods
|
||||
const getTableList = async () => {
|
||||
const id = (query.id as unknown as number) && (query.jobId as unknown as number)
|
||||
tableObject.paramsObj.params = {
|
||||
jobId: id
|
||||
}
|
||||
await getList()
|
||||
}
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('定时任务日志.xls')
|
||||
}
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('') // 弹出层标题
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: JobLogVO) => {
|
||||
// 设置数据
|
||||
const res = JobLogApi.getJobLogApi(row.id)
|
||||
detailRef.value = res
|
||||
dialogTitle.value = t('action.detail')
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
onMounted(() => {
|
||||
getTableList()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button
|
||||
type="warning"
|
||||
v-hasPermi="['infra:job:export']"
|
||||
:loading="tableObject.exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #beginTime="{ row }">
|
||||
<span>{{
|
||||
dayjs(row.beginTime).format('YYYY-MM-DD HH:mm:ss') +
|
||||
' ~ ' +
|
||||
dayjs(row.endTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</span>
|
||||
</template>
|
||||
<template #duration="{ row }">
|
||||
<span>{{ row.duration + ' 毫秒' }}</span>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button link type="primary" v-hasPermi="['infra:job:query']" @click="handleDetail(row)">
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #retryInterval="{ row }">
|
||||
<span>{{ row.retryInterval + '毫秒' }} </span>
|
||||
</template>
|
||||
<template #monitorTimeout="{ row }">
|
||||
<span>{{ row.monitorTimeout > 0 ? row.monitorTimeout + ' 毫秒' : '未开启' }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
215
yudao-ui-admin-vue3/src/views/infra/job/index.vue
Normal file
215
yudao-ui-admin-vue3/src/views/infra/job/index.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref } from 'vue'
|
||||
import DictTag from '@/components/DictTag/src/DictTag.vue'
|
||||
import * as JobApi from '@/api/infra/job'
|
||||
import { JobVO } from '@/api/infra/job/types'
|
||||
import Icon from '@/components/Icon/src/Icon.vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import { rules, allSchemas } from './job.data'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter()
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<JobVO>, JobVO>({
|
||||
getListApi: JobApi.getJobPageApi,
|
||||
delListApi: JobApi.deleteJobApi,
|
||||
exportListApi: JobApi.exportJobApi
|
||||
})
|
||||
const { getList, setSearchParams, delList, exportList } = methods
|
||||
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('定时任务.xls')
|
||||
}
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: JobVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await JobApi.getJobApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
// 执行日志
|
||||
const handleJobLog = (row: JobVO) => {
|
||||
if (row.id) {
|
||||
push('/job/job-log?id=' + row.id)
|
||||
} else {
|
||||
push('/job/job-log')
|
||||
}
|
||||
}
|
||||
// 执行一次
|
||||
const handleRun = (row: JobVO) => {
|
||||
ElMessageBox.confirm('确认要立即执行一次' + row.name + '?', t('common.reminder'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
JobApi.runJobApi(row.id).then(() => {
|
||||
ElMessage.success('执行成功')
|
||||
getList()
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as JobVO
|
||||
if (actionType.value === 'create') {
|
||||
await JobApi.createJobApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await JobApi.updateJobApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: JobVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: JobVO) => {
|
||||
// 设置数据
|
||||
const res = JobApi.getJobApi(row.id)
|
||||
detailRef.value = res
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['infra:job:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
v-hasPermi="['infra:job:export']"
|
||||
:loading="tableObject.exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
<el-button type="info" v-hasPermi="['infra:job:query']" @click="handleJobLog">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> 执行日志
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button link type="primary" v-hasPermi="['infra:job:update']" @click="handleUpdate(row)">
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button link type="primary" v-hasPermi="['infra:job:query']" @click="handleDetail(row)">
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button link type="primary" v-hasPermi="['infra:job:delete']" @click="handleDelete(row)">
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
<el-button link type="primary" v-hasPermi="['infra:job:trigger']" @click="handleRun(row)">
|
||||
<Icon icon="ep:view" class="mr-5px" /> 执行一次
|
||||
</el-button>
|
||||
<el-button link type="primary" v-hasPermi="['infra:job:query']" @click="handleJobLog(row)">
|
||||
<Icon icon="ep:view" class="mr-5px" /> 调度日志
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #retryInterval="{ row }">
|
||||
<span>{{ row.retryInterval + '毫秒' }} </span>
|
||||
</template>
|
||||
<template #monitorTimeout="{ row }">
|
||||
<span>{{ row.monitorTimeout > 0 ? row.monitorTimeout + ' 毫秒' : '未开启' }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
93
yudao-ui-admin-vue3/src/views/infra/job/job.data.ts
Normal file
93
yudao-ui-admin-vue3/src/views/infra/job/job.data.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { reactive } from 'vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
handlerName: [required],
|
||||
cronExpression: [required],
|
||||
retryCount: [required],
|
||||
retryInterval: [required]
|
||||
})
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '任务名称',
|
||||
field: 'name',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.INFRA_JOB_STATUS
|
||||
},
|
||||
{
|
||||
label: '处理器的名字',
|
||||
field: 'handlerName',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '处理器的参数',
|
||||
field: 'handlerParam',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'CRON 表达式',
|
||||
field: 'cronExpression'
|
||||
},
|
||||
{
|
||||
label: '重试次数',
|
||||
field: 'retryCount',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '重试间隔',
|
||||
field: 'retryInterval',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '监控超时时间',
|
||||
field: 'monitorTimeout',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '400px',
|
||||
label: t('table.action'),
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
94
yudao-ui-admin-vue3/src/views/infra/job/jobLog.data.ts
Normal file
94
yudao-ui-admin-vue3/src/views/infra/job/jobLog.data.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { reactive } from 'vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '任务编号',
|
||||
field: 'jobId',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '处理器的名字',
|
||||
field: 'handlerName',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '处理器的参数',
|
||||
field: 'handlerParam'
|
||||
},
|
||||
{
|
||||
label: '第几次执行',
|
||||
field: 'executeIndex'
|
||||
},
|
||||
{
|
||||
label: '开始执行时间',
|
||||
field: 'beginTime',
|
||||
search: {
|
||||
show: true,
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '结束执行时间',
|
||||
field: 'endTime',
|
||||
search: {
|
||||
show: true,
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '执行时长',
|
||||
field: 'duration'
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.INFRA_JOB_LOG_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '100px',
|
||||
label: t('table.action'),
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
205
yudao-ui-admin-vue3/src/views/infra/redis/index.vue
Normal file
205
yudao-ui-admin-vue3/src/views/infra/redis/index.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
import * as RedisApi from '@/api/infra/redis'
|
||||
import DictTag from '@/components/DictTag/src/DictTag.vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import * as echarts from 'echarts'
|
||||
import { RedisKeyInfo, RedisMonitorInfoVO } from '@/api/infra/redis/types'
|
||||
import {
|
||||
ElRow,
|
||||
ElCard,
|
||||
ElCol,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElScrollbar,
|
||||
ElDescriptions,
|
||||
ElDescriptionsItem
|
||||
} from 'element-plus'
|
||||
const cache = ref<RedisMonitorInfoVO>()
|
||||
const keyListLoad = ref(true)
|
||||
const keyList = ref<RedisKeyInfo[]>([])
|
||||
// 基本信息
|
||||
const readRedisInfo = async () => {
|
||||
const data = await RedisApi.redisMonitorInfo()
|
||||
cache.value = data
|
||||
loadEchartOptions(cache.value.commandStats)
|
||||
const redisKeysInfo = await RedisApi.redisKeysInfo()
|
||||
keyList.value = redisKeysInfo
|
||||
keyListLoad.value = false //加载完成
|
||||
}
|
||||
// 图表
|
||||
const commandStatsRef = ref<HTMLElement>()
|
||||
|
||||
const usedmemory = ref<HTMLDivElement>()
|
||||
|
||||
const loadEchartOptions = (stats) => {
|
||||
const commandStats = [] as any[]
|
||||
const nameList = [] as string[]
|
||||
stats.forEach((row) => {
|
||||
commandStats.push({
|
||||
name: row.command,
|
||||
value: row.calls
|
||||
})
|
||||
nameList.push(row.command)
|
||||
})
|
||||
|
||||
const commandStatsInstance = echarts.init(commandStatsRef.value!, 'macarons')
|
||||
|
||||
commandStatsInstance.setOption({
|
||||
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: nameList,
|
||||
textStyle: {
|
||||
color: '#a1a1a1'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '命令',
|
||||
type: 'pie',
|
||||
radius: [20, 120],
|
||||
center: ['40%', '60%'],
|
||||
data: commandStats,
|
||||
roseType: 'radius',
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const usedMemoryInstance = echarts.init(usedmemory.value!, 'macarons')
|
||||
usedMemoryInstance.setOption({
|
||||
title: {
|
||||
text: '内存使用情况',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
formatter: '{b} <br/>{a} : ' + cache.value!.info.used_memory_human
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '峰值',
|
||||
type: 'gauge',
|
||||
min: 0,
|
||||
max: 100,
|
||||
progress: {
|
||||
show: true
|
||||
},
|
||||
detail: {
|
||||
formatter: cache.value!.info.used_memory_human
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: parseFloat(cache.value!.info.used_memory_human),
|
||||
name: '内存消耗'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
readRedisInfo()
|
||||
})
|
||||
</script>
|
||||
<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" style="margin-top: 10px">
|
||||
<el-card :gutter="12" shadow="hover">
|
||||
<div ref="commandStatsRef" style="height: 350px"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12" style="margin-top: 10px">
|
||||
<el-card style="margin-left: 10px" :gutter="12" shadow="hover">
|
||||
<div ref="usedmemory" style="height: 350px"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 10px">
|
||||
<el-col :span="24" class="card-box" shadow="hover">
|
||||
<el-card>
|
||||
<el-table v-loading="keyListLoad" :data="keyList" row-key="id">
|
||||
<el-table-column prop="keyTemplate" label="Key 模板" width="200" />
|
||||
<el-table-column prop="keyType" label="Key 类型" width="100" />
|
||||
<el-table-column prop="valueType" label="Value 类型" />
|
||||
<el-table-column prop="timeoutType" label="超时时间" width="200">
|
||||
<template #default="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_REDIS_TIMEOUT_TYPE" :value="row?.timeoutType" />
|
||||
<span v-if="row?.timeout > 0">({{ row?.timeout / 1000 }} 秒)</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="memo" label="备注" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
12
yudao-ui-admin-vue3/src/views/infra/server/index.vue
Normal file
12
yudao-ui-admin-vue3/src/views/infra/server/index.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { IFrame } from '@/components/IFrame'
|
||||
import { ref } from 'vue'
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const src = ref(BASE_URL + '/admin/applications')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<IFrame :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
11
yudao-ui-admin-vue3/src/views/infra/skywalking/index.vue
Normal file
11
yudao-ui-admin-vue3/src/views/infra/skywalking/index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { IFrame } from '@/components/IFrame'
|
||||
import { ref } from 'vue'
|
||||
const src = ref('http://skywalking.shop.iocoder.cn')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<IFrame :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
12
yudao-ui-admin-vue3/src/views/infra/swagger/index.vue
Normal file
12
yudao-ui-admin-vue3/src/views/infra/swagger/index.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { IFrame } from '@/components/IFrame'
|
||||
import { ref } from 'vue'
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const src = ref(BASE_URL + '/doc.html')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<IFrame :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
7
yudao-ui-admin-vue3/src/views/infra/testDemo/index.vue
Normal file
7
yudao-ui-admin-vue3/src/views/infra/testDemo/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>index</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
70
yudao-ui-admin-vue3/src/views/pay/app/app.data.ts
Normal file
70
yudao-ui-admin-vue3/src/views/pay/app/app.data.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
code: [required],
|
||||
sort: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '应用名',
|
||||
field: 'name',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '商户名称',
|
||||
field: 'payMerchant',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
198
yudao-ui-admin-vue3/src/views/pay/app/index.vue
Normal file
198
yudao-ui-admin-vue3/src/views/pay/app/index.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { AppVO } from '@/api/pay/app/types'
|
||||
import { rules, allSchemas } from './app.data'
|
||||
import * as AppApi from '@/api/pay/app'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<AppVO>, AppVO>({
|
||||
getListApi: AppApi.getAppPageApi,
|
||||
delListApi: AppApi.deleteAppApi,
|
||||
exportListApi: AppApi.exportAppApi
|
||||
})
|
||||
const { getList, setSearchParams, delList, exportList } = methods
|
||||
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('应用数据.xls')
|
||||
}
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: AppVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await AppApi.getAppApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as AppVO
|
||||
if (actionType.value === 'create') {
|
||||
await AppApi.createAppApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await AppApi.updateAppApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: AppVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: AppVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:post:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
v-hasPermi="['system:post:export']"
|
||||
:loading="tableObject.exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
198
yudao-ui-admin-vue3/src/views/pay/merchant/index.vue
Normal file
198
yudao-ui-admin-vue3/src/views/pay/merchant/index.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { MerchantVO } from '@/api/pay/merchant/types'
|
||||
import { rules, allSchemas } from './merchant.data'
|
||||
import * as MerchantApi from '@/api/pay/merchant'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<MerchantVO>, MerchantVO>({
|
||||
getListApi: MerchantApi.getMerchantPageApi,
|
||||
delListApi: MerchantApi.deleteMerchantApi,
|
||||
exportListApi: MerchantApi.exportMerchantApi
|
||||
})
|
||||
const { getList, setSearchParams, delList, exportList } = methods
|
||||
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('商户数据.xls')
|
||||
}
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: MerchantVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await MerchantApi.getMerchantApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as MerchantVO
|
||||
if (actionType.value === 'create') {
|
||||
await MerchantApi.createMerchantApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await MerchantApi.updateMerchantApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: MerchantVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: MerchantVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:post:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
v-hasPermi="['system:post:export']"
|
||||
:loading="tableObject.exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
94
yudao-ui-admin-vue3/src/views/pay/merchant/merchant.data.ts
Normal file
94
yudao-ui-admin-vue3/src/views/pay/merchant/merchant.data.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
code: [required],
|
||||
sort: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '商户号',
|
||||
field: 'no',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '商户全称',
|
||||
field: 'code',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '商户简称',
|
||||
field: 'shortName',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('form.remark'),
|
||||
field: 'remark',
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
177
yudao-ui-admin-vue3/src/views/pay/order/index.vue
Normal file
177
yudao-ui-admin-vue3/src/views/pay/order/index.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { OrderVO } from '@/api/pay/order/types'
|
||||
import { rules, allSchemas } from './order.data'
|
||||
import * as OrderApi from '@/api/pay/order'
|
||||
const { t } = useI18n() // 国际化
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<OrderVO>, OrderVO>({
|
||||
getListApi: OrderApi.getOrderPageApi,
|
||||
delListApi: OrderApi.deleteOrderApi,
|
||||
exportListApi: OrderApi.exportOrderApi
|
||||
})
|
||||
const { getList, setSearchParams, delList, exportList } = methods
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('订单数据.xls')
|
||||
}
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: OrderVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await OrderApi.getOrderApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as OrderVO
|
||||
if (actionType.value === 'create') {
|
||||
await OrderApi.createOrderApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await OrderApi.updateOrderApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: OrderVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: OrderVO) => {
|
||||
setDialogTile('detail')
|
||||
// 设置数据
|
||||
const res = await OrderApi.getOrderApi(row.id as number)
|
||||
detailRef.value = res
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['pay:order:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
v-hasPermi="['pay:order:export']"
|
||||
:loading="tableObject.exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button link type="primary" v-hasPermi="['pay:order:update']" @click="handleUpdate(row)">
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button link type="primary" v-hasPermi="['pay:order:update']" @click="handleDetail(row)">
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button link type="primary" v-hasPermi="['pay:order:delete']" @click="handleDelete(row)">
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
169
yudao-ui-admin-vue3/src/views/pay/order/order.data.ts
Normal file
169
yudao-ui-admin-vue3/src/views/pay/order/order.data.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
merchantId: [required],
|
||||
appId: [required],
|
||||
merchantOrderId: [required],
|
||||
subject: [required],
|
||||
body: [required],
|
||||
notifyUrl: [required],
|
||||
notifyStatus: [required],
|
||||
amount: [required],
|
||||
status: [required],
|
||||
userIp: [required],
|
||||
expireTime: [required],
|
||||
refundStatus: [required],
|
||||
refundTimes: [required],
|
||||
refundAmount: [required]
|
||||
})
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '商户编号',
|
||||
field: 'merchantId',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '应用编号',
|
||||
field: 'appId',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '渠道编号',
|
||||
field: 'channelId'
|
||||
},
|
||||
{
|
||||
label: '渠道编码',
|
||||
field: 'channelCode',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '渠道订单号',
|
||||
field: 'merchantOrderId',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '商品标题',
|
||||
field: 'subject'
|
||||
},
|
||||
{
|
||||
label: '商品描述',
|
||||
field: 'body'
|
||||
},
|
||||
{
|
||||
label: '异步通知地址',
|
||||
field: 'notifyUrl'
|
||||
},
|
||||
{
|
||||
label: '回调商户状态',
|
||||
field: 'notifyStatus',
|
||||
dictType: DICT_TYPE.PAY_ORDER_NOTIFY_STATUS
|
||||
},
|
||||
{
|
||||
label: '支付金额',
|
||||
field: 'amount',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '渠道手续费',
|
||||
field: 'channelFeeRate',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '渠道手续金额',
|
||||
field: 'channelFeeAmount',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '支付状态',
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.PAY_ORDER_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '用户 IP',
|
||||
field: 'userIp'
|
||||
},
|
||||
{
|
||||
label: '订单失效时间',
|
||||
field: 'expireTime'
|
||||
},
|
||||
{
|
||||
label: '订单支付成功时间',
|
||||
field: 'successTime'
|
||||
},
|
||||
{
|
||||
label: '订单支付通知时间',
|
||||
field: 'notifyTime'
|
||||
},
|
||||
{
|
||||
label: '支付成功的订单拓展单编号',
|
||||
field: 'successExtensionId'
|
||||
},
|
||||
{
|
||||
label: '退款状态',
|
||||
field: 'refundStatus',
|
||||
dictType: DICT_TYPE.PAY_ORDER_REFUND_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '退款次数',
|
||||
field: 'refundTimes'
|
||||
},
|
||||
{
|
||||
label: '退款总金额',
|
||||
field: 'refundAmount'
|
||||
},
|
||||
{
|
||||
label: '渠道用户编号',
|
||||
field: 'channelUserId'
|
||||
},
|
||||
{
|
||||
label: '渠道订单号',
|
||||
field: 'channelOrderNo'
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '270px',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
121
yudao-ui-admin-vue3/src/views/pay/refund/index.vue
Normal file
121
yudao-ui-admin-vue3/src/views/pay/refund/index.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { RefundVO } from '@/api/pay/refund/types'
|
||||
import { allSchemas } from './refund.data'
|
||||
import * as RefundApi from '@/api/pay/refund'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<RefundVO>, RefundVO>({
|
||||
getListApi: RefundApi.getRefundPageApi,
|
||||
delListApi: RefundApi.deleteRefundApi,
|
||||
exportListApi: RefundApi.exportRefundApi
|
||||
})
|
||||
const { getList, setSearchParams, delList, exportList } = methods
|
||||
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('退款订单.xls')
|
||||
}
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: RefundVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: RefundVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = RefundApi.getRefundApi(row.id)
|
||||
dialogTitle.value = t('action.detail')
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button
|
||||
type="warning"
|
||||
v-hasPermi="['system:post:export']"
|
||||
:loading="tableObject.exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
101
yudao-ui-admin-vue3/src/views/pay/refund/refund.data.ts
Normal file
101
yudao-ui-admin-vue3/src/views/pay/refund/refund.data.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '应用编号',
|
||||
field: 'appId',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '商户名称',
|
||||
field: 'merchantName'
|
||||
},
|
||||
{
|
||||
label: '应用名称',
|
||||
field: 'appName'
|
||||
},
|
||||
{
|
||||
label: '商品名称',
|
||||
field: 'subject'
|
||||
},
|
||||
{
|
||||
label: '商户退款单号',
|
||||
field: 'merchantRefundNo'
|
||||
},
|
||||
{
|
||||
label: '商户订单号',
|
||||
field: 'merchantOrderId'
|
||||
},
|
||||
{
|
||||
label: '交易订单号',
|
||||
field: 'tradeNo'
|
||||
},
|
||||
{
|
||||
label: '支付金额',
|
||||
field: 'payAmount'
|
||||
},
|
||||
{
|
||||
label: '退款金额',
|
||||
field: 'refundAmount'
|
||||
},
|
||||
{
|
||||
label: '渠道编码',
|
||||
field: 'channelCode',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '退款类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.PAY_REFUND_ORDER_TYPE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.PAY_REFUND_ORDER_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
55
yudao-ui-admin-vue3/src/views/system/dept/dept.data.ts
Normal file
55
yudao-ui-admin-vue3/src/views/system/dept/dept.data.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { required } from '@/utils/formRules'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export const modelSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
label: '上级部门',
|
||||
field: 'parentId',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
label: '部门名称',
|
||||
field: 'name',
|
||||
component: 'Input',
|
||||
formItemProps: {
|
||||
rules: [required]
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '负责人',
|
||||
field: 'email',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
label: '联系电话',
|
||||
field: 'phone',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
label: '邮箱',
|
||||
field: 'email',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
label: '显示排序',
|
||||
field: 'sort',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
field: 'status',
|
||||
component: 'RadioButton',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '开启',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '关闭',
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
])
|
||||
189
yudao-ui-admin-vue3/src/views/system/dept/index.vue
Normal file
189
yudao-ui-admin-vue3/src/views/system/dept/index.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElCard, ElTree, ElTreeSelect, ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import { onMounted, ref, unref } from 'vue'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import { Form, FormExpose } from '@/components/Form'
|
||||
import { modelSchema } from './dept.data'
|
||||
import { DeptVO } from '@/api/system/dept/types'
|
||||
interface Tree {
|
||||
id: number
|
||||
name: string
|
||||
children?: Tree[]
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id'
|
||||
}
|
||||
const { t } = useI18n() // 国际化
|
||||
const loading = ref(false) // 遮罩层
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const showForm = ref(false) // 显示form表单
|
||||
const formTitle = ref('部门信息') // 显示form标题
|
||||
const deptParentId = ref(0) // 上级ID
|
||||
// 创建form表单
|
||||
const formRef = ref<FormExpose>()
|
||||
|
||||
// ========== 创建部门树结构 ==========
|
||||
const deptOptions = ref([]) // 树形结构
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>()
|
||||
const getTree = async () => {
|
||||
const res = await DeptApi.listSimpleDeptApi()
|
||||
deptOptions.value = handleTree(res)
|
||||
}
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true
|
||||
return data.name.includes(value)
|
||||
}
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
// 重置表单
|
||||
formTitle.value = '新增部门'
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
showForm.value = true
|
||||
}
|
||||
// 编辑
|
||||
const handleUpdate = async (data: { id: number }) => {
|
||||
showForm.value = true
|
||||
const res = await DeptApi.getDeptApi(data.id)
|
||||
formTitle.value = '修改- ' + res?.name
|
||||
deptParentId.value = res.parentId
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
// 删除
|
||||
const handleDelete = async (data: { id: number }) => {
|
||||
ElMessageBox.confirm(t('common.delDataMessage'), t('common.confirmTitle'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
await DeptApi.deleteDeptApi(data.id)
|
||||
ElMessage.success(t('common.delSuccess'))
|
||||
})
|
||||
.catch(() => {})
|
||||
await getTree()
|
||||
}
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
loading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as DeptVO
|
||||
deptParentId.value = data.parentId
|
||||
// TODO: 表单提交待完善
|
||||
if (formTitle.value.startsWith('新增')) {
|
||||
await DeptApi.createDeptApi(data)
|
||||
} else if (formTitle.value.startsWith('修改')) {
|
||||
await DeptApi.updateDeptApi(data)
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getTree()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex">
|
||||
<el-card class="w-1/3 dept" :gutter="12" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>部门列表</span>
|
||||
<el-button type="primary" v-hasPermi="['system:dept:create']" @click="handleAdd">
|
||||
新增根节点
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="custom-tree-container">
|
||||
<!-- <p>部门列表</p> -->
|
||||
<!-- 操作工具栏 -->
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="id"
|
||||
:data="deptOptions"
|
||||
:props="defaultProps"
|
||||
:highlight-current="true"
|
||||
default-expand-all
|
||||
:filter-method="filterNode"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node">
|
||||
<span>{{ node.label }}</span>
|
||||
<span>
|
||||
<el-button link v-hasPermi="['system:dept:create']" @click="handleAdd()">
|
||||
<Icon icon="ep:plus" class="mr-5px" />
|
||||
</el-button>
|
||||
<el-button link v-hasPermi="['system:dept:update']" @click="handleUpdate(data)">
|
||||
<Icon icon="ep:edit" class="mr-5px" />
|
||||
</el-button>
|
||||
<el-button link v-hasPermi="['system:dept:delete']" @click="handleDelete(data)">
|
||||
<Icon icon="ep:delete" class="mr-5px" />
|
||||
</el-button>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card class="w-2/3 dept" style="margin-left: 10px" :gutter="12" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ formTitle }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="!showForm">
|
||||
<span><p>请从左侧选择部门</p></span>
|
||||
</div>
|
||||
<div v-if="showForm">
|
||||
<!-- 操作工具栏 -->
|
||||
<Form :schema="modelSchema" ref="formRef">
|
||||
<template #parentId>
|
||||
<el-tree-select
|
||||
node-key="id"
|
||||
v-model="deptParentId"
|
||||
:props="defaultProps"
|
||||
:data="deptOptions"
|
||||
check-strictly
|
||||
/>
|
||||
</template>
|
||||
</Form>
|
||||
<!-- 操作按钮 -->
|
||||
<el-button
|
||||
type="primary"
|
||||
v-hasPermi="['system:dept:update']"
|
||||
:loading="loading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button type="danger" @click="showForm = false">取消</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.dept {
|
||||
height: 600px;
|
||||
max-height: 1800px;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.custom-tree-node {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
</style>
|
||||
128
yudao-ui-admin-vue3/src/views/system/dict/dict.data.ts
Normal file
128
yudao-ui-admin-vue3/src/views/system/dict/dict.data.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { reactive } from 'vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 表单校验
|
||||
export const dictDataRules = reactive({
|
||||
dictType: [required],
|
||||
label: [required],
|
||||
value: [required]
|
||||
})
|
||||
// crudSchemas
|
||||
export const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '字典类型',
|
||||
field: 'dictType',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '数据标签',
|
||||
field: 'label',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '数据键值',
|
||||
field: 'value'
|
||||
},
|
||||
{
|
||||
label: '颜色类型',
|
||||
field: 'colorType',
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'default',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
label: 'success',
|
||||
value: 'success'
|
||||
},
|
||||
{
|
||||
label: 'info',
|
||||
value: 'info'
|
||||
},
|
||||
{
|
||||
label: 'warning',
|
||||
value: 'warning'
|
||||
},
|
||||
{
|
||||
label: 'danger',
|
||||
value: 'danger'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'CSS Class',
|
||||
field: 'cssClass',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '显示排序',
|
||||
field: 'sort',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS
|
||||
},
|
||||
{
|
||||
label: t('form.remark'),
|
||||
field: 'remark',
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '180px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
74
yudao-ui-admin-vue3/src/views/system/dict/dict.type.ts
Normal file
74
yudao-ui-admin-vue3/src/views/system/dict/dict.type.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { reactive } from 'vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 表单校验
|
||||
export const dictTypeRules = reactive({
|
||||
name: [required],
|
||||
type: [required]
|
||||
})
|
||||
// 新增 + 修改
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '字典名称',
|
||||
field: 'name',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '字典类型',
|
||||
field: 'type',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS
|
||||
},
|
||||
{
|
||||
label: t('form.remark'),
|
||||
field: 'remark',
|
||||
table: {
|
||||
show: false
|
||||
},
|
||||
form: {
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '180px',
|
||||
label: t('table.action'),
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
298
yudao-ui-admin-vue3/src/views/system/dict/index.vue
Normal file
298
yudao-ui-admin-vue3/src/views/system/dict/index.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref, onMounted } from 'vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import * as DictTypeSchemas from './dict.type'
|
||||
import * as DictDataSchemas from './dict.data'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { ElCard, ElMessage } from 'element-plus'
|
||||
import * as DictTypeApi from '@/api/system/dict/dict.type'
|
||||
import * as DictDataApi from '@/api/system/dict/dict.data'
|
||||
import { DictDataVO, DictTypeVO } from '@/api/system/dict/types'
|
||||
const { t } = useI18n() // 国际化
|
||||
// ========== 字典分类列表相关 ==========
|
||||
const {
|
||||
register: typeRegister,
|
||||
tableObject: typeTableObject,
|
||||
methods: typeMethods
|
||||
} = useTable<PageResult<DictTypeVO>, DictTypeVO>({
|
||||
getListApi: DictTypeApi.getDictTypePageApi,
|
||||
delListApi: DictTypeApi.deleteDictTypeApi
|
||||
})
|
||||
const {
|
||||
getList: getTypeList,
|
||||
setSearchParams: setTypeSearchParams,
|
||||
delList: delTypeList
|
||||
} = typeMethods
|
||||
|
||||
// 字典分类修改操作
|
||||
const handleTypeCreate = () => {
|
||||
setDialogTile('typeCreate')
|
||||
// 重置表单
|
||||
unref(typeFormRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
const handleTypeUpdate = async (row: DictTypeVO) => {
|
||||
setDialogTile('typeUpdate')
|
||||
// 设置数据
|
||||
const res = await DictTypeApi.getDictTypeApi(row.id)
|
||||
unref(typeFormRef)?.setValues(res)
|
||||
}
|
||||
// 字典分类删除操作
|
||||
const handleTypeDelete = async (row: DictTypeVO) => {
|
||||
await delTypeList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 字典数据列表相关 ==========
|
||||
const tableTypeSelect = ref(false)
|
||||
const {
|
||||
register: dataRegister,
|
||||
tableObject: dataTableObject,
|
||||
methods: dataMethods
|
||||
} = useTable<PageResult<DictDataVO>, DictDataVO>({
|
||||
getListApi: DictDataApi.getDictDataPageApi,
|
||||
delListApi: DictDataApi.deleteDictDataApi
|
||||
})
|
||||
const {
|
||||
getList: getDataList,
|
||||
setSearchParams: setDataSearchParams,
|
||||
delList: delDataList
|
||||
} = dataMethods
|
||||
|
||||
// 字典数据修改操作
|
||||
const handleDataCreate = () => {
|
||||
setDialogTile('dataCreate')
|
||||
// 重置表单
|
||||
unref(dataFormRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
const handleDataUpdate = async (row: DictDataVO) => {
|
||||
setDialogTile('dataUpdate')
|
||||
// 设置数据
|
||||
const res = await DictDataApi.getDictDataApi(row.id)
|
||||
unref(dataFormRef)?.setValues(res)
|
||||
}
|
||||
// 字典数据删除操作
|
||||
const handleDataDelete = async (row: DictTypeVO) => {
|
||||
await delDataList(row.id, false)
|
||||
}
|
||||
// 字典分类点击行事件
|
||||
const parentType = ref('')
|
||||
const onClickType = async (data: { [key: string]: any }) => {
|
||||
tableTypeSelect.value = true
|
||||
dataTableObject.paramsObj.params = {
|
||||
dictType: data.type
|
||||
}
|
||||
getDataList()
|
||||
parentType.value = data.type
|
||||
}
|
||||
// 弹出框
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const typeFormRef = ref<FormExpose>() // 分类表单 Ref
|
||||
const dataFormRef = ref<FormExpose>() // 数据表单 Ref
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
// 提交按钮
|
||||
const submitTypeForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(typeFormRef)?.formModel as DictTypeVO
|
||||
if (actionType.value === 'typeCreate') {
|
||||
await DictTypeApi.createDictTypeApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else if (actionType.value === 'typeUpdate') {
|
||||
await DictTypeApi.updateDictTypeApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
await getTypeList()
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
const submitDataForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(dataFormRef)?.formModel as DictDataVO
|
||||
if (actionType.value === 'dataCreate') {
|
||||
data.dictType = parentType.value
|
||||
await DictDataApi.createDictDataApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else if (actionType.value === 'dataUpdate') {
|
||||
await DictDataApi.updateDictDataApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
await getDataList()
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
// 初始化查询
|
||||
onMounted(async () => {
|
||||
await getTypeList()
|
||||
typeTableObject.tableList[0] && onClickType(typeTableObject.tableList[0])
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex">
|
||||
<el-card class="w-1/2 dict" :gutter="12" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>字典分类</span>
|
||||
</div>
|
||||
</template>
|
||||
<Search
|
||||
:schema="DictTypeSchemas.allSchemas.searchSchema"
|
||||
@search="setTypeSearchParams"
|
||||
@reset="setTypeSearchParams"
|
||||
/>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:dict:create']" @click="handleTypeCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
@row-click="onClickType"
|
||||
:columns="DictTypeSchemas.allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="typeTableObject.tableList"
|
||||
:loading="typeTableObject.loading"
|
||||
:pagination="{
|
||||
total: typeTableObject.total
|
||||
}"
|
||||
:highlight-current-row="true"
|
||||
v-model:pageSize="typeTableObject.pageSize"
|
||||
v-model:currentPage="typeTableObject.currentPage"
|
||||
@register="typeRegister"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:dict:update']"
|
||||
@click="handleTypeUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:dict:delete']"
|
||||
@click="handleTypeDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</el-card>
|
||||
<el-card class="w-1/2 dict" style="margin-left: 10px" :gutter="12" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>字典数据</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 列表 -->
|
||||
<div v-if="!tableTypeSelect">
|
||||
<span>请从左侧选择</span>
|
||||
</div>
|
||||
<div v-if="tableTypeSelect">
|
||||
<Search
|
||||
:schema="DictDataSchemas.allSchemas.searchSchema"
|
||||
@search="setDataSearchParams"
|
||||
@reset="setDataSearchParams"
|
||||
/>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:dict:create']" @click="handleDataCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<Table
|
||||
:columns="DictDataSchemas.allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="dataTableObject.tableList"
|
||||
:loading="dataTableObject.loading"
|
||||
:pagination="{
|
||||
total: dataTableObject.total
|
||||
}"
|
||||
v-model:pageSize="dataTableObject.pageSize"
|
||||
v-model:currentPage="dataTableObject.currentPage"
|
||||
@register="dataRegister"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:dict:update']"
|
||||
@click="handleDataUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:dict:delete']"
|
||||
@click="handleDataDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</el-card>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<Form
|
||||
v-if="['typeCreate', 'typeUpdate'].includes(actionType)"
|
||||
:schema="DictTypeSchemas.allSchemas.formSchema"
|
||||
:rules="DictTypeSchemas.dictTypeRules"
|
||||
ref="typeFormRef"
|
||||
/>
|
||||
<Form
|
||||
v-if="['dataCreate', 'dataUpdate'].includes(actionType)"
|
||||
:schema="DictDataSchemas.allSchemas.formSchema"
|
||||
:rules="DictDataSchemas.dictDataRules"
|
||||
ref="dataFormRef"
|
||||
/>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['typeCreate', 'typeUpdate'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitTypeForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="['dataCreate', 'dataUpdate'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitDataForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,94 @@
|
||||
import { reactive } from 'vue'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
applicationName: [required],
|
||||
code: [required],
|
||||
message: [required]
|
||||
})
|
||||
// 新增 + 修改
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '错误码类型',
|
||||
field: 'type',
|
||||
component: 'InputNumber',
|
||||
dictType: DICT_TYPE.SYSTEM_ERROR_CODE_TYPE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '应用名',
|
||||
field: 'applicationName',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '错误码编码',
|
||||
field: 'code',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '错误码错误提示',
|
||||
field: 'message'
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'daterange',
|
||||
table: {
|
||||
show: false
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
label: t('table.action'),
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
184
yudao-ui-admin-vue3/src/views/system/errorCode/index.vue
Normal file
184
yudao-ui-admin-vue3/src/views/system/errorCode/index.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { ErrorCodeVO } from '@/api/system/errorCode/types'
|
||||
import { rules, allSchemas } from './errorCode.data'
|
||||
import * as ErrorCodeApi from '@/api/system/errorCode'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<ErrorCodeVO>, ErrorCodeVO>({
|
||||
getListApi: ErrorCodeApi.getErrorCodePageApi,
|
||||
delListApi: ErrorCodeApi.deleteErrorCodeApi
|
||||
})
|
||||
const { getList, setSearchParams, delList } = methods
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const loading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: ErrorCodeVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await ErrorCodeApi.getErrorCodeApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
loading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as ErrorCodeVO
|
||||
if (actionType.value === 'create') {
|
||||
await ErrorCodeApi.createErrorCodeApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ErrorCodeApi.updateErrorCodeApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: ErrorCodeVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: ErrorCodeVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button v-hasPermi="['system:error-code:create']" type="primary" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #type="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_ERROR_CODE_TYPE" :value="row.type" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:error-code:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:error-code:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:error-code:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #type="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_ERROR_CODE_TYPE" :value="row.type" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
80
yudao-ui-admin-vue3/src/views/system/loginlog/index.vue
Normal file
80
yudao-ui-admin-vue3/src/views/system/loginlog/index.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { allSchemas } from './loginLog.data'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import type { LoginLogVO } from '@/api/system/loginLog/types'
|
||||
import { getLoginLogPageApi, exportLoginLogApi } from '@/api/system/loginLog'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<LoginLogVO>, LoginLogVO>({
|
||||
getListApi: getLoginLogPageApi,
|
||||
exportListApi: exportLoginLogApi
|
||||
})
|
||||
const { getList, setSearchParams } = methods
|
||||
// 详情操作
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref(t('action.detail')) // 弹出层标题
|
||||
const detailRef = ref() // 详情 Ref
|
||||
const handleDetail = async (row: LoginLogVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
getList()
|
||||
</script>
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #logType="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="row.logType" />
|
||||
</template>
|
||||
<template #result="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="row.result" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button link type="primary" @click="handleDetail(row)">
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" maxHeight="500px" width="50%">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
|
||||
<template #logType="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="row.logType" />
|
||||
</template>
|
||||
<template #result="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="row.result" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,92 @@
|
||||
import { reactive } from 'vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '日志类型',
|
||||
field: 'logType',
|
||||
dictType: DICT_TYPE.SYSTEM_LOGIN_TYPE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '用户名称',
|
||||
field: 'username',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '登录地址',
|
||||
field: 'userIp',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'userAgent',
|
||||
field: 'userAgent'
|
||||
},
|
||||
{
|
||||
label: '登陆结果',
|
||||
field: 'result',
|
||||
dictType: DICT_TYPE.SYSTEM_LOGIN_RESULT,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'daterange',
|
||||
table: {
|
||||
show: false
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '80px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
404
yudao-ui-admin-vue3/src/views/system/menu/index.vue
Normal file
404
yudao-ui-admin-vue3/src/views/system/menu/index.vue
Normal file
@@ -0,0 +1,404 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import dayjs from 'dayjs'
|
||||
import { IconSelect } from '@/components/Icon'
|
||||
import { Tooltip } from '@/components/Tooltip'
|
||||
import * as MenuApi from '@/api/system/menu'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import {
|
||||
ElRow,
|
||||
ElCol,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElInputNumber,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElMessageBox,
|
||||
ElMessage,
|
||||
ElCascader,
|
||||
ElRadioGroup,
|
||||
ElRadioButton
|
||||
} from 'element-plus'
|
||||
import { MenuVO } from '@/api/system/menu/types'
|
||||
import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
// ========== 创建菜单树结构 ==========
|
||||
const loading = ref(true)
|
||||
const menuData = ref([]) // 树形结构
|
||||
const getList = async () => {
|
||||
const res = await MenuApi.getMenuListApi(queryParams)
|
||||
menuData.value = handleTree(res)
|
||||
loading.value = false
|
||||
}
|
||||
const menuProps = {
|
||||
checkStrictly: true,
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id'
|
||||
}
|
||||
const menuOptions = ref([]) // 树形结构
|
||||
const getTree = async () => {
|
||||
const res = await MenuApi.listSimpleMenusApi()
|
||||
menuOptions.value = handleTree(res)
|
||||
}
|
||||
// ========== 查询 ==========
|
||||
const queryParams = reactive({
|
||||
name: undefined,
|
||||
status: undefined
|
||||
})
|
||||
// 查询操作
|
||||
const handleQuery = async () => {
|
||||
await getList()
|
||||
}
|
||||
// 重置操作
|
||||
const resetQuery = async () => {
|
||||
queryParams.name = undefined
|
||||
queryParams.status = undefined
|
||||
await getList()
|
||||
}
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const menuForm = ref<MenuVO>({
|
||||
id: 0,
|
||||
name: '',
|
||||
permission: '',
|
||||
type: SystemMenuTypeEnum.DIR,
|
||||
sort: 1,
|
||||
parentId: 0,
|
||||
path: '',
|
||||
icon: '',
|
||||
component: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
visible: true,
|
||||
keepAlive: true,
|
||||
createTime: ''
|
||||
})
|
||||
// 表单校验
|
||||
const rules = {
|
||||
name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
|
||||
path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
}
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
// 新建操作
|
||||
const handleCreate = () => {
|
||||
// 重置表单
|
||||
setDialogTile('create')
|
||||
}
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: MenuVO) => {
|
||||
// 设置数据
|
||||
const res = await MenuApi.getMenuApi(row.id)
|
||||
menuForm.value = res
|
||||
setDialogTile('update')
|
||||
}
|
||||
// 删除操作
|
||||
const handleDelete = async (row: MenuVO) => {
|
||||
ElMessageBox.confirm(t('common.delDataMessage'), t('common.confirmTitle'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
await MenuApi.deleteMenuApi(row.id)
|
||||
ElMessage.success(t('common.delSuccess'))
|
||||
})
|
||||
.catch(() => {})
|
||||
await getList()
|
||||
}
|
||||
// 保存操作
|
||||
function isExternal(path: string) {
|
||||
return /^(https?:|mailto:|tel:)/.test(path)
|
||||
}
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
if (
|
||||
menuForm.value.type === SystemMenuTypeEnum.DIR ||
|
||||
menuForm.value.type === SystemMenuTypeEnum.MENU
|
||||
) {
|
||||
if (!isExternal(menuForm.value.path)) {
|
||||
if (menuForm.value.parentId === 0 && menuForm.value.path.charAt(0) !== '/') {
|
||||
ElMessage.error('路径必须以 / 开头')
|
||||
return
|
||||
} else if (menuForm.value.parentId !== 0 && menuForm.value.path.charAt(0) === '/') {
|
||||
ElMessage.error('路径不能以 / 开头')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if (actionType.value === 'create') {
|
||||
await MenuApi.createMenuApi(menuForm.value)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await MenuApi.updateMenuApi(menuForm.value)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
// ========== 初始化 ==========
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
getTree()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true">
|
||||
<el-form-item label="菜单名称" prop="name">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入菜单名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择菜单状态">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px" />
|
||||
{{ t('common.query') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh-right" class="mr-5px" />
|
||||
{{ t('common.reset') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:notice:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
table-layout="auto"
|
||||
row-key="id"
|
||||
:data="menuData"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
>
|
||||
<el-table-column label="菜单名称" prop="name" width="240px">
|
||||
<template #default="scope">
|
||||
<Icon :icon="scope.row.icon" />
|
||||
<span class="ml-3">{{ scope.row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="菜单类型" prop="type">
|
||||
<template #default="scope">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_MENU_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="路由地址" prop="path" />
|
||||
<el-table-column label="组件路径" prop="component" />
|
||||
<el-table-column label="权限标识" prop="permission" />
|
||||
<el-table-column label="排序" prop="sort" />
|
||||
<el-table-column label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime">
|
||||
<template #default="scope">
|
||||
<span>{{ dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:menu:update']"
|
||||
@click="handleUpdate(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:menu:delete']"
|
||||
@click="handleDelete(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
<!-- 添加或修改菜单对话框 -->
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" maxHeight="400px" width="40%">
|
||||
<el-form
|
||||
:model="menuForm"
|
||||
:rules="rules"
|
||||
:inline="true"
|
||||
label-width="120px"
|
||||
label-position="right"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="上级菜单">
|
||||
<el-cascader
|
||||
:options="menuData"
|
||||
:props="menuProps"
|
||||
placeholder="请选择上级菜单"
|
||||
v-model="menuForm.parentId"
|
||||
class="w-100"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="菜单类型" prop="type">
|
||||
<el-radio-group v-model="menuForm.type">
|
||||
<el-radio-button
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="菜单名称" prop="name">
|
||||
<el-input v-model="menuForm.name" placeholder="请输入菜单名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<template v-if="menuForm.type !== 3">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="菜单图标">
|
||||
<IconSelect v-model="menuForm.icon" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="路由地址" prop="path">
|
||||
<template #label>
|
||||
<Tooltip
|
||||
titel="路由地址"
|
||||
message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
|
||||
/>
|
||||
</template>
|
||||
<el-input v-model="menuForm.path" placeholder="请输入路由地址" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<template v-if="menuForm.type === 2">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="路由地址" prop="component">
|
||||
<el-input v-model="menuForm.component" placeholder="请输入组件地址" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<template v-if="menuForm.type !== 1">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="权限标识" prop="permission">
|
||||
<template #label>
|
||||
<Tooltip
|
||||
titel="权限标识"
|
||||
message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
|
||||
/>
|
||||
</template>
|
||||
<el-input v-model="menuForm.permission" placeholder="请输入权限标识" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="显示排序" prop="sort">
|
||||
<el-input-number v-model="menuForm.sort" controls-position="right" :min="0" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="菜单状态" prop="status">
|
||||
<el-radio-group v-model="menuForm.status">
|
||||
<el-radio-button
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<template v-if="menuForm.type !== 3">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="显示状态" prop="status">
|
||||
<template #label>
|
||||
<Tooltip
|
||||
titel="显示状态"
|
||||
message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问"
|
||||
/>
|
||||
</template>
|
||||
<el-radio-group v-model="menuForm.visible">
|
||||
<el-radio-button key="true" :label="true">显示</el-radio-button>
|
||||
<el-radio-button key="false" :label="false">隐藏</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<template v-if="menuForm.type === 2">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="缓存状态" prop="keepAlive">
|
||||
<template #label>
|
||||
<Tooltip
|
||||
titel="缓存状态"
|
||||
message="选择缓存时,则会被 `keep-alive` 缓存,需要匹配组件的 `name` 和路由地址保持一致"
|
||||
/>
|
||||
</template>
|
||||
<el-radio-group v-model="menuForm.keepAlive">
|
||||
<el-radio-button key="true" :label="true">缓存</el-radio-button>
|
||||
<el-radio-button key="false" :label="false">不缓存</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
:deep(.el-button.is-text) {
|
||||
margin-left: 0;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
</style>
|
||||
166
yudao-ui-admin-vue3/src/views/system/menu/menu.data.ts
Normal file
166
yudao-ui-admin-vue3/src/views/system/menu/menu.data.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { reactive } from 'vue'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 修改
|
||||
export const modelSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
label: '上级菜单',
|
||||
field: 'parentId',
|
||||
component: 'Input',
|
||||
formItemProps: {
|
||||
rules: [required]
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '菜单类型',
|
||||
field: 'type',
|
||||
component: 'RadioButton',
|
||||
formItemProps: {
|
||||
rules: [required]
|
||||
},
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '目录',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: '菜单',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '按钮',
|
||||
value: 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '菜单图标',
|
||||
field: 'icon',
|
||||
component: 'Input',
|
||||
formItemProps: {
|
||||
rules: [required]
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '菜单名称',
|
||||
field: 'name',
|
||||
component: 'Input',
|
||||
formItemProps: {
|
||||
rules: [required]
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '显示排序',
|
||||
field: 'sort',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
label: '路由地址',
|
||||
field: 'path',
|
||||
component: 'Input',
|
||||
labelMessage: '访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头'
|
||||
},
|
||||
{
|
||||
label: '组件路径',
|
||||
field: 'component',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
label: '权限标识',
|
||||
field: 'permission',
|
||||
component: 'Input',
|
||||
labelMessage:
|
||||
'Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission(`system:user:list`)`)'
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
component: 'RadioButton',
|
||||
value: 0,
|
||||
formItemProps: {
|
||||
rules: [required]
|
||||
},
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '开启',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '关闭',
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
labelMessage: '选择停用时,路由将不会出现在侧边栏,也不能被访问'
|
||||
},
|
||||
{
|
||||
label: '是否显示',
|
||||
field: 'visible',
|
||||
component: 'RadioButton',
|
||||
value: 0,
|
||||
formItemProps: {
|
||||
rules: [required]
|
||||
},
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '显示',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '隐藏',
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
labelMessage: '选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问'
|
||||
},
|
||||
{
|
||||
label: '是否缓存',
|
||||
field: 'keepAlive',
|
||||
component: 'RadioButton',
|
||||
value: 0,
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '缓存',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '不缓存',
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
labelMessage: '选择缓存时,则会被 `keep-alive` 缓存,需要匹配组件的 `name` 和路由地址保持一致'
|
||||
}
|
||||
])
|
||||
// 列表
|
||||
export const columns = reactive<TableColumn[]>([
|
||||
{
|
||||
label: '菜单名称',
|
||||
field: 'name'
|
||||
},
|
||||
{
|
||||
label: '权限标识',
|
||||
field: 'permission',
|
||||
component: 'Input',
|
||||
formItemProps: {
|
||||
rules: [required]
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '排序',
|
||||
field: 'sort'
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '180px'
|
||||
}
|
||||
])
|
||||
226
yudao-ui-admin-vue3/src/views/system/menu/old.vue
Normal file
226
yudao-ui-admin-vue3/src/views/system/menu/old.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, unref } from 'vue'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { IconSelect } from '@/components/Icon'
|
||||
import { ElCard, ElMessage, ElMessageBox, ElTree, ElTreeSelect } from 'element-plus'
|
||||
import { columns, modelSchema } from './menu.data'
|
||||
import { Form, FormExpose } from '@/components/Form'
|
||||
import * as MenuApi from '@/api/system/menu'
|
||||
import { MenuVO } from '@/api/system/menu/types'
|
||||
const { t } = useI18n() // 国际化
|
||||
interface Tree {
|
||||
id: number
|
||||
name: string
|
||||
children?: Tree[]
|
||||
}
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id'
|
||||
}
|
||||
// ========== 创建菜单树结构 ==========
|
||||
const menuOptions = ref([]) // 树形结构
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>()
|
||||
const getTree = async () => {
|
||||
const res = await MenuApi.listSimpleMenusApi()
|
||||
menuOptions.value = handleTree(res)
|
||||
}
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true
|
||||
return data.name.includes(value)
|
||||
}
|
||||
// ========== 菜单信息form表单 ==========
|
||||
const loading = ref(false) // 遮罩层
|
||||
const formRef = ref<FormExpose>()
|
||||
const iconModel = ref('ep:user')
|
||||
const menuParentId = ref()
|
||||
const menuStatus = ref('add')
|
||||
const menuTitle = ref('菜单信息')
|
||||
const showEdit = ref(false)
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
loading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as MenuVO
|
||||
data.parentId = menuParentId.value
|
||||
// TODO: 表单提交待完善
|
||||
if (menuStatus.value === 'add') {
|
||||
await MenuApi.createMenuApi(data)
|
||||
} else if (menuStatus.value === 'edit') {
|
||||
await MenuApi.updateMenuApi(data)
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
// ========== 按钮列表相关 ==========
|
||||
const tableData = ref([])
|
||||
const tableLoading = ref(false)
|
||||
const onDisabled = ref(true)
|
||||
const tableTitle = ref('按钮信息')
|
||||
// 树点击事件
|
||||
const handleMenuNodeClick = async (data: { [key: string]: any }) => {
|
||||
showEdit.value = true
|
||||
const res = await MenuApi.getMenuApi(data.id)
|
||||
menuTitle.value = res.name + '-菜单信息'
|
||||
tableTitle.value = res.name + '-按钮列表'
|
||||
menuParentId.value = data.id
|
||||
tableData.value = await MenuApi.getMenuListApi({ name: res.name })
|
||||
unref(formRef)?.setValues(res)
|
||||
onDisabled.value = true
|
||||
changeDisabled()
|
||||
}
|
||||
const handleCreate = () => {
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
menuParentId.value = 0
|
||||
onDisabled.value = false
|
||||
changeDisabled()
|
||||
}
|
||||
const handleEdit = () => {
|
||||
onDisabled.value = false
|
||||
changeDisabled()
|
||||
}
|
||||
const changeDisabled = () => {
|
||||
unref(formRef)?.setProps({
|
||||
disabled: onDisabled
|
||||
})
|
||||
}
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: MenuVO) => {
|
||||
// 设置数据
|
||||
const res = await MenuApi.getMenuApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: MenuVO) => {
|
||||
ElMessageBox.confirm(t('common.delDataMessage'), t('common.confirmTitle'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
await MenuApi.deleteMenuApi(row.id)
|
||||
ElMessage.success(t('common.delSuccess'))
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getTree()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex">
|
||||
<el-card class="w-1/4 menu" :gutter="12" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>菜单列表</span>
|
||||
<el-button type="primary" v-hasPermi="['system:menu:create']" @click="handleCreate">
|
||||
新增根节点
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<!-- <p>菜单列表</p> -->
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="id"
|
||||
:accordion="true"
|
||||
:data="menuOptions"
|
||||
:props="defaultProps"
|
||||
:highlight-current="true"
|
||||
:filter-method="filterNode"
|
||||
@node-click="handleMenuNodeClick"
|
||||
/>
|
||||
</el-card>
|
||||
<el-card class="w-1/2 menu" style="margin-left: 10px" :gutter="12" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ menuTitle }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="!showEdit">
|
||||
<span>请从左侧选择菜单</span>
|
||||
</div>
|
||||
<div v-if="showEdit">
|
||||
<Form :loading="loading" :schema="modelSchema" ref="formRef">
|
||||
<template #parentId>
|
||||
<el-tree-select
|
||||
node-key="id"
|
||||
v-model="menuParentId"
|
||||
:props="defaultProps"
|
||||
:data="menuOptions"
|
||||
check-strictly
|
||||
/>
|
||||
</template>
|
||||
<template #icon>
|
||||
<IconSelect v-model="iconModel" />
|
||||
</template>
|
||||
</Form>
|
||||
<el-button
|
||||
v-if="!onDisabled"
|
||||
type="primary"
|
||||
v-hasPermi="['system:menu:update']"
|
||||
:loading="loading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button v-if="!onDisabled" :loading="loading" @click="showEdit = false">
|
||||
{{ t('common.cancel') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="onDisabled"
|
||||
v-hasPermi="['system:menu:update']"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleEdit"
|
||||
>
|
||||
{{ t('action.edit') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card class="w-1/2 menu" style="margin-left: 10px" :gutter="12" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ tableTitle }}</span>
|
||||
<!-- <el-button type="primary">新增根节点</el-button> -->
|
||||
</div>
|
||||
</template>
|
||||
<!-- 列表 -->
|
||||
<Table :loading="tableLoading" :columns="columns" :data="tableData">
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:menu:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:menu:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.menu {
|
||||
height: 1000px;
|
||||
max-height: 1800px;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
189
yudao-ui-admin-vue3/src/views/system/notice/index.vue
Normal file
189
yudao-ui-admin-vue3/src/views/system/notice/index.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { NoticeVO } from '@/api/system/notice/types'
|
||||
import { rules, allSchemas } from './notice.data'
|
||||
import * as NoticeApi from '@/api/system/notice'
|
||||
const { t } = useI18n() // 国际化
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<NoticeVO>, NoticeVO>({
|
||||
getListApi: NoticeApi.getNoticePageApi,
|
||||
delListApi: NoticeApi.deleteNoticeApi
|
||||
})
|
||||
const { getList, setSearchParams, delList } = methods
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: NoticeVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await NoticeApi.getNoticeApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as NoticeVO
|
||||
if (actionType.value === 'create') {
|
||||
await NoticeApi.createNoticeApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await NoticeApi.updateNoticeApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: NoticeVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: NoticeVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:notice:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #type="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_NOTICE_TYPE" :value="row.type" />
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:notice:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:notice:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:notice:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" maxHeight="500px" width="50%">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #type="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_NOTICE_TYPE" :value="row.type" />
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
86
yudao-ui-admin-vue3/src/views/system/notice/notice.data.ts
Normal file
86
yudao-ui-admin-vue3/src/views/system/notice/notice.data.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
title: [required],
|
||||
type: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '公告标题',
|
||||
field: 'title',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '公告类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.SYSTEM_NOTICE_TYPE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
form: {
|
||||
component: 'RadioButton'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '公告内容',
|
||||
field: 'content',
|
||||
form: {
|
||||
component: 'Editor',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
valueHtml: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
@@ -0,0 +1,141 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n()
|
||||
// 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
clientId: [required],
|
||||
secret: [required],
|
||||
name: [required],
|
||||
logo: [required],
|
||||
status: [required],
|
||||
accessTokenValiditySeconds: [required],
|
||||
refreshTokenValiditySeconds: [required],
|
||||
redirectUris: [required],
|
||||
authorizedGrantTypes: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '客户端编号',
|
||||
field: 'clientId',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '客户端密钥',
|
||||
field: 'secret'
|
||||
},
|
||||
{
|
||||
label: '应用名',
|
||||
field: 'name',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '应用图标',
|
||||
field: 'logo'
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '访问令牌的有效期',
|
||||
field: 'accessTokenValiditySeconds'
|
||||
},
|
||||
{
|
||||
label: '刷新令牌的有效期',
|
||||
field: 'refreshTokenValiditySeconds'
|
||||
},
|
||||
{
|
||||
label: '授权类型',
|
||||
field: 'authorizedGrantTypes',
|
||||
dictType: DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE
|
||||
},
|
||||
{
|
||||
label: '授权范围',
|
||||
field: 'scopes',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '自动授权范围',
|
||||
field: 'autoApproveScopes',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '可重定向的 URI 地址',
|
||||
field: 'redirectUris',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '权限',
|
||||
field: 'authorities',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '资源',
|
||||
field: 'resourceIds',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '附加信息',
|
||||
field: 'additionalInformation',
|
||||
table: {
|
||||
show: false
|
||||
},
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
187
yudao-ui-admin-vue3/src/views/system/oauth2/client/index.vue
Normal file
187
yudao-ui-admin-vue3/src/views/system/oauth2/client/index.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage, ElImage } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { OAuth2ClientVo } from '@/api/system/oauth2/client.types'
|
||||
import { rules, allSchemas } from './client.data'
|
||||
import * as ClientApi from '@/api/system/oauth2/client'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<OAuth2ClientVo>, OAuth2ClientVo>({
|
||||
getListApi: ClientApi.getOAuth2ClientPageApi,
|
||||
delListApi: ClientApi.deleteOAuth2ClientApi
|
||||
})
|
||||
const { getList, setSearchParams, delList } = methods
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: OAuth2ClientVo) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await ClientApi.getOAuth2ClientApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as OAuth2ClientVo
|
||||
if (actionType.value === 'create') {
|
||||
await ClientApi.createOAuth2ClientApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ClientApi.updateOAuth2ClientApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: OAuth2ClientVo) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: OAuth2ClientVo) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:oauth2-client:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #logo="{ row }">
|
||||
<el-image :src="row.logo" />
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:oauth2-client:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:oauth2-client:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:oauth2-client:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
92
yudao-ui-admin-vue3/src/views/system/oauth2/token/index.vue
Normal file
92
yudao-ui-admin-vue3/src/views/system/oauth2/token/index.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { allSchemas } from './token.data'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { OAuth2TokenVo } from '@/api/system/oauth2/token.types'
|
||||
import * as TokenApi from '@/api/system/oauth2/token'
|
||||
import { ref } from 'vue'
|
||||
const { t } = useI18n() // 国际化
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<OAuth2TokenVo>, OAuth2TokenVo>({
|
||||
getListApi: TokenApi.getAccessTokenPageApi,
|
||||
delListApi: TokenApi.deleteAccessTokenApi
|
||||
})
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref(t('action.detail')) // 弹出层标题
|
||||
const { getList, setSearchParams, delList } = methods
|
||||
// 详情
|
||||
const handleDetail = (row: OAuth2TokenVo) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
// 强退操作
|
||||
const handleForceLogout = (row: OAuth2TokenVo) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
getList()
|
||||
</script>
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #userType="{ row }">
|
||||
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #expiresTime="{ row }">
|
||||
<span>{{ dayjs(row.expiresTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button link type="primary" @click="handleDetail(row)">
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:oauth2-token:delete']"
|
||||
@click="handleForceLogout(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.logout') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
|
||||
<template #userType="{ row }">
|
||||
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #expiresTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,68 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '用户编号',
|
||||
field: 'userId',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '访问令牌',
|
||||
field: 'accessToken'
|
||||
},
|
||||
{
|
||||
label: '刷新令牌',
|
||||
field: 'refreshToken'
|
||||
},
|
||||
{
|
||||
label: '用户类型',
|
||||
field: 'userType',
|
||||
dictType: DICT_TYPE.USER_TYPE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '过期时间',
|
||||
field: 'expiresTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '200px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
101
yudao-ui-admin-vue3/src/views/system/operatelog/index.vue
Normal file
101
yudao-ui-admin-vue3/src/views/system/operatelog/index.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { allSchemas } from './operateLog.data'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { OperateLogVO } from '@/api/system/operateLog/types'
|
||||
import * as OperateLogApi from '@/api/system/operateLog'
|
||||
import { ref } from 'vue'
|
||||
const { t } = useI18n() // 国际化
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<OperateLogVO>, OperateLogVO>({
|
||||
getListApi: OperateLogApi.getOperateLogPageApi,
|
||||
exportListApi: OperateLogApi.exportOperateLogApi
|
||||
})
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref(t('action.detail')) // 弹出层标题
|
||||
const { getList, setSearchParams, exportList } = methods
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('数据.xls')
|
||||
}
|
||||
// 详情
|
||||
const handleDetail = (row: OperateLogVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
getList()
|
||||
</script>
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button
|
||||
type="warning"
|
||||
v-hasPermi="['system:operate-log:export']"
|
||||
:loading="tableObject.exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #type="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_OPERATE_TYPE" :value="row.type" />
|
||||
</template>
|
||||
<template #duration="{ row }">
|
||||
<span>{{ row.duration + 'ms' }}</span>
|
||||
</template>
|
||||
<template #resultCode="{ row }">
|
||||
<span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span>
|
||||
</template>
|
||||
<template #startTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button link type="primary" @click="handleDetail(row)">
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
|
||||
<template #resultCode="{ row }">
|
||||
<span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span>
|
||||
</template>
|
||||
<template #type="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_OPERATE_TYPE" :value="row.type" />
|
||||
</template>
|
||||
<template #duration="{ row }">
|
||||
<span>{{ row.duration + 'ms' }}</span>
|
||||
</template>
|
||||
<template #startTime="{ row }">
|
||||
<span>{{ dayjs(row.startTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,112 @@
|
||||
import { reactive } from 'vue'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '操作模块',
|
||||
field: 'module',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '操作名',
|
||||
field: 'name'
|
||||
},
|
||||
{
|
||||
label: '操作类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.SYSTEM_OPERATE_TYPE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '请求方法名',
|
||||
field: 'requestMethod'
|
||||
},
|
||||
{
|
||||
label: '请求地址',
|
||||
field: 'requestUrl'
|
||||
},
|
||||
{
|
||||
label: '操作人员',
|
||||
field: 'userNickname'
|
||||
},
|
||||
{
|
||||
label: '操作明细',
|
||||
field: 'content',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '用户 IP',
|
||||
field: 'userIp',
|
||||
table: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'userAgent',
|
||||
field: 'userAgent'
|
||||
},
|
||||
{
|
||||
label: '操作结果',
|
||||
field: 'resultCode'
|
||||
},
|
||||
{
|
||||
label: '操作日期',
|
||||
field: 'startTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '执行时长',
|
||||
field: 'duration'
|
||||
},
|
||||
{
|
||||
label: '操作日期',
|
||||
field: 'daterange',
|
||||
table: {
|
||||
show: false
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '80px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
198
yudao-ui-admin-vue3/src/views/system/post/index.vue
Normal file
198
yudao-ui-admin-vue3/src/views/system/post/index.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { PostVO } from '@/api/system/post/types'
|
||||
import { rules, allSchemas } from './post.data'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<PostVO>, PostVO>({
|
||||
getListApi: PostApi.getPostPageApi,
|
||||
delListApi: PostApi.deletePostApi,
|
||||
exportListApi: PostApi.exportPostApi
|
||||
})
|
||||
const { getList, setSearchParams, delList, exportList } = methods
|
||||
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('用户数据.xls')
|
||||
}
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: PostVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await PostApi.getPostApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as PostVO
|
||||
if (actionType.value === 'create') {
|
||||
await PostApi.createPostApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await PostApi.updatePostApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: PostVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: PostVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:post:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
v-hasPermi="['system:post:export']"
|
||||
:loading="tableObject.exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
74
yudao-ui-admin-vue3/src/views/system/post/post.data.ts
Normal file
74
yudao-ui-admin-vue3/src/views/system/post/post.data.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
code: [required],
|
||||
sort: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '岗位名称',
|
||||
field: 'name',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '岗位编码',
|
||||
field: 'code',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '岗位顺序',
|
||||
field: 'sort'
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
327
yudao-ui-admin-vue3/src/views/system/role/index.vue
Normal file
327
yudao-ui-admin-vue3/src/views/system/role/index.vue
Normal file
@@ -0,0 +1,327 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { RoleVO } from '@/api/system/role/types'
|
||||
import { rules, allSchemas } from './role.data'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import Dialog from '@/components/Dialog/src/Dialog.vue'
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElMessage,
|
||||
ElTree,
|
||||
ElCard,
|
||||
ElCheckbox
|
||||
} from 'element-plus'
|
||||
import { listSimpleMenusApi } from '@/api/system/menu'
|
||||
import { listSimpleDeptApi } from '@/api/system/dept'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import { SystemDataScopeEnum } from '@/utils/constants'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<RoleVO>, RoleVO>({
|
||||
getListApi: RoleApi.getRolePageApi,
|
||||
delListApi: RoleApi.deleteRoleApi
|
||||
})
|
||||
const { getList, setSearchParams, delList } = methods
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const loading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: RoleVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await RoleApi.getRoleApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
loading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as RoleVO
|
||||
if (actionType.value === 'create') {
|
||||
await RoleApi.createRoleApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await RoleApi.updateRoleApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: RoleVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: RoleVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 数据权限 ==========
|
||||
const dataScopeForm = reactive({
|
||||
name: '',
|
||||
code: '',
|
||||
dataScope: 0,
|
||||
checkStrictly: true,
|
||||
checkList: []
|
||||
})
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id'
|
||||
}
|
||||
const treeOptions = ref([]) // 菜单树形结构
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>()
|
||||
const dialogScopeVisible = ref(false)
|
||||
const dialogScopeTitle = ref('数据权限')
|
||||
const actionScopeType = ref('')
|
||||
const dataScopeDictDatas = ref()
|
||||
// 选项
|
||||
const treeNodeAll = ref(false)
|
||||
// 权限操作
|
||||
const handleScope = async (type: string, row: RoleVO) => {
|
||||
dataScopeForm.name = row.name
|
||||
dataScopeForm.code = row.code
|
||||
if (type === 'menu') {
|
||||
const menuRes = await listSimpleMenusApi()
|
||||
treeOptions.value = handleTree(menuRes)
|
||||
dataScopeDictDatas.value = getDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE)
|
||||
} else if (type === 'dept') {
|
||||
const deptRes = await listSimpleDeptApi()
|
||||
treeOptions.value = handleTree(deptRes)
|
||||
}
|
||||
actionScopeType.value = type
|
||||
dialogScopeVisible.value = true
|
||||
}
|
||||
// 树权限(父子联动)
|
||||
const handleCheckedTreeConnect = (value) => {
|
||||
dataScopeForm.checkStrictly = value ? true : false
|
||||
}
|
||||
// 全选/全不选
|
||||
const handleCheckedTreeNodeAll = (value) => {
|
||||
treeRef.value?.setCheckedNodes(value ? dataScopeForm.checkList : [])
|
||||
}
|
||||
// TODO:保存
|
||||
const submitScope = () => {
|
||||
console.info()
|
||||
}
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:role:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #type="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_ROLE_TYPE" :value="row.type" />
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:role:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:role:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:permission:assign-role-menu']"
|
||||
@click="handleScope('menu', row)"
|
||||
>
|
||||
<Icon icon="ep:basketball" class="mr-5px" /> 菜单权限
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:permission:assign-role-data-scope']"
|
||||
@click="handleScope('data', row)"
|
||||
>
|
||||
<Icon icon="ep:coin" class="mr-5px" /> 数据权限
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:role:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #type="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_ROLE_TYPE" :value="row.type" />
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
<Dialog v-model="dialogScopeVisible" :title="dialogScopeTitle">
|
||||
<el-form :model="dataScopeForm">
|
||||
<el-form-item label="角色名称">
|
||||
<el-input v-model="dataScopeForm.name" :disabled="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色标识">
|
||||
<el-input v-model="dataScopeForm.code" :disabled="true" />
|
||||
</el-form-item>
|
||||
<!-- 分配角色的数据权限对话框 -->
|
||||
<el-form-item label="权限范围" v-if="actionScopeType === 'data'">
|
||||
<el-select v-model="dataScopeForm.dataScope">
|
||||
<el-option
|
||||
v-for="item in dataScopeDictDatas"
|
||||
:key="parseInt(item.value)"
|
||||
:label="item.label"
|
||||
:value="parseInt(item.value)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 分配角色的菜单权限对话框 -->
|
||||
<el-form-item
|
||||
label="权限范围"
|
||||
v-if="
|
||||
actionScopeType === 'menu' || dataScopeForm.dataScope === SystemDataScopeEnum.DEPT_CUSTOM
|
||||
"
|
||||
>
|
||||
<el-card class="box-card">
|
||||
<template #header>
|
||||
<el-checkbox
|
||||
:checked="!dataScopeForm.checkStrictly"
|
||||
@change="handleCheckedTreeConnect($event)"
|
||||
>父子联动(选中父节点,自动选择子节点)
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="treeNodeAll" @change="handleCheckedTreeNodeAll($event)">
|
||||
全选/全不选
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
:check-strictly="dataScopeForm.checkStrictly"
|
||||
:props="defaultProps"
|
||||
:data="treeOptions"
|
||||
empty-text="加载中,请稍后"
|
||||
/>
|
||||
</el-card>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button type="primary" :loading="loading" @click="submitScope">
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogScopeVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
89
yudao-ui-admin-vue3/src/views/system/role/role.data.ts
Normal file
89
yudao-ui-admin-vue3/src/views/system/role/role.data.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
code: [required],
|
||||
sort: [required]
|
||||
})
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '角色名称',
|
||||
field: 'name',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '角色类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.SYSTEM_ROLE_TYPE
|
||||
},
|
||||
{
|
||||
label: '角色标识',
|
||||
field: 'code',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '显示顺序',
|
||||
field: 'sort',
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '450px',
|
||||
label: t('table.action'),
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
222
yudao-ui-admin-vue3/src/views/system/sensitiveWord/index.vue
Normal file
222
yudao-ui-admin-vue3/src/views/system/sensitiveWord/index.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage, ElTag, ElSelect, ElOption } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { SensitiveWordVO } from '@/api/system/sensitiveWord/types'
|
||||
import { rules, allSchemas } from './sensitiveWord.data'
|
||||
import * as SensitiveWordApi from '@/api/system/sensitiveWord'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<SensitiveWordVO>, SensitiveWordVO>({
|
||||
getListApi: SensitiveWordApi.getSensitiveWordPageApi,
|
||||
delListApi: SensitiveWordApi.deleteSensitiveWordApi,
|
||||
exportListApi: SensitiveWordApi.exportSensitiveWordApi
|
||||
})
|
||||
const { getList, setSearchParams, delList, exportList } = methods
|
||||
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList('敏感词数据.xls')
|
||||
}
|
||||
// 获取标签
|
||||
const tagsOptions = ref()
|
||||
const getTags = async () => {
|
||||
const res = await SensitiveWordApi.getSensitiveWordTagsApi()
|
||||
tagsOptions.value = res
|
||||
}
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const tags = ref()
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: SensitiveWordVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await SensitiveWordApi.getSensitiveWordApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as SensitiveWordVO
|
||||
if (actionType.value === 'create') {
|
||||
await SensitiveWordApi.createSensitiveWordApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SensitiveWordApi.updateSensitiveWordApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: SensitiveWordVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: SensitiveWordVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
onMounted(async () => {
|
||||
await getTags()
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:post:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
v-hasPermi="['system:post:export']"
|
||||
:loading="tableObject.exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #tags="{ row }">
|
||||
<el-tag
|
||||
:disable-transitions="true"
|
||||
:key="index"
|
||||
v-for="(tag, index) in row.tags"
|
||||
:index="index"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:post:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
>
|
||||
<template #tags>
|
||||
<el-select v-model="tags" multiple placeholder="请选择">
|
||||
<el-option v-for="item in tagsOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</template>
|
||||
</Form>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,79 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
tags: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '敏感词',
|
||||
field: 'name',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '标签',
|
||||
field: 'tags'
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '描述',
|
||||
field: 'description',
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
190
yudao-ui-admin-vue3/src/views/system/sms/smsChannel/index.vue
Normal file
190
yudao-ui-admin-vue3/src/views/system/sms/smsChannel/index.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
import type { SmsChannelVO } from '@/api/system/sms/smsChannel/types'
|
||||
import { rules, allSchemas } from './sms.channel.data'
|
||||
import * as SmsChannelApi from '@/api/system/sms/smsChannel'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const { register, tableObject, methods } = useTable<PageResult<SmsChannelVO>, SmsChannelVO>({
|
||||
getListApi: SmsChannelApi.getSmsChannelPageApi,
|
||||
delListApi: SmsChannelApi.deleteSmsChannelApi
|
||||
})
|
||||
const { getList, setSearchParams, delList } = methods
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const loading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
unref(formRef)?.getElFormRef()?.resetFields()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (row: SmsChannelVO) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await SmsChannelApi.getSmsChannelApi(row.id)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
loading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as SmsChannelVO
|
||||
if (actionType.value === 'create') {
|
||||
await SmsChannelApi.createSmsChannelApi(data)
|
||||
ElMessage.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SmsChannelApi.updateSmsChannelApi(data)
|
||||
ElMessage.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
await getList()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = (row: SmsChannelVO) => {
|
||||
delList(row.id, false)
|
||||
}
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailRef = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (row: SmsChannelVO) => {
|
||||
// 设置数据
|
||||
detailRef.value = row
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 搜索工作区 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
</ContentWrap>
|
||||
<ContentWrap>
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" v-hasPermi="['system:sms-channel:create']" @click="handleCreate">
|
||||
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<Table
|
||||
:columns="allSchemas.tableColumns"
|
||||
:selection="false"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
@register="register"
|
||||
>
|
||||
<template #code="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="row.code" />
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:sms-channel:update']"
|
||||
@click="handleUpdate(row)"
|
||||
>
|
||||
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:sms-channel:update']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['system:sms-channel:delete']"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailRef"
|
||||
>
|
||||
<template #code="{ row }">
|
||||
<DictTag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="row.code" />
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||
</template>
|
||||
<template #createTime="{ row }">
|
||||
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="submitForm"
|
||||
>
|
||||
{{ t('action.save') }}
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,104 @@
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
signature: [required],
|
||||
code: [required],
|
||||
apiKey: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: t('common.index'),
|
||||
field: 'id',
|
||||
type: 'index',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '短信签名',
|
||||
field: 'signature',
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '渠道编码',
|
||||
field: 'code',
|
||||
dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
search: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '短信 API 的账号',
|
||||
field: 'apiKey'
|
||||
},
|
||||
{
|
||||
label: '短信 API 的密钥',
|
||||
field: 'apiSecret'
|
||||
},
|
||||
{
|
||||
label: '短信发送回调 URL',
|
||||
field: 'callbackUrl'
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
field: 'daterange',
|
||||
table: {
|
||||
show: false
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('table.action'),
|
||||
field: 'action',
|
||||
width: '240px',
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user