init
This commit is contained in:
8
src/views/Error/403.vue
Normal file
8
src/views/Error/403.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<Error type="403" @error-click="push('/')" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'Error403' })
|
||||
|
||||
const { push } = useRouter()
|
||||
</script>
|
||||
7
src/views/Error/404.vue
Normal file
7
src/views/Error/404.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<Error @error-click="push('/')" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'Error404' })
|
||||
const { push } = useRouter()
|
||||
</script>
|
||||
7
src/views/Error/500.vue
Normal file
7
src/views/Error/500.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<Error type="500" @error-click="push('/')" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'Error500' })
|
||||
const { push } = useRouter()
|
||||
</script>
|
||||
384
src/views/Home/Index.vue
Normal file
384
src/views/Home/Index.vue
Normal file
@@ -0,0 +1,384 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card shadow="never">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<el-row :gutter="16" justify="space-between">
|
||||
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex items-center">
|
||||
<el-avatar :src="avatar" :size="70" class="mr-16px">
|
||||
<img src="@/assets/imgs/avatar.jpg" alt="" />
|
||||
</el-avatar>
|
||||
<div>
|
||||
<div class="text-20px">
|
||||
{{ t('workplace.welcome') }} {{ 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="h-70px flex items-center justify-end lt-sm:mt-10px">
|
||||
<div class="px-8px text-right">
|
||||
<div class="mb-16px text-14px text-gray-400">{{ 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="mb-16px text-14px text-gray-400">{{ 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="mb-16px text-14px text-gray-400">{{ 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-8px" :gutter="8" justify="space-between">
|
||||
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="h-3 flex justify-between">
|
||||
<span>{{ t('workplace.project') }}</span>
|
||||
<el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
|
||||
</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="8"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
>
|
||||
<el-card shadow="hover">
|
||||
<div class="flex items-center">
|
||||
<Icon :icon="item.icon" :size="25" class="mr-8px" />
|
||||
<span class="text-16px">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="mt-16px text-14px text-gray-400">{{ t(item.message) }}</div>
|
||||
<div class="mt-16px flex justify-between text-12px text-gray-400">
|
||||
<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-8px">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<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-8px">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :options="pieOptionsData" :height="280" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
|
||||
<el-card shadow="hover" class="mb-8px">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :options="barOptionsData" :height="280" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-8px">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="h-3 flex justify-between">
|
||||
<span>{{ t('workplace.shortcutOperation') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<el-row>
|
||||
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">
|
||||
<div class="flex items-center">
|
||||
<Icon :icon="item.icon" class="mr-8px" />
|
||||
<el-link type="default" :underline="false" @click="setWatermark(item.name)">
|
||||
{{ item.name }}
|
||||
</el-link>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="mt-8px">
|
||||
<template #header>
|
||||
<div class="h-3 flex justify-between">
|
||||
<span>{{ t('workplace.notice') }}</span>
|
||||
<el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
|
||||
</div>
|
||||
</template>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
|
||||
<div class="flex items-center">
|
||||
<el-avatar :src="avatar" :size="35" class="mr-16px">
|
||||
<img src="@/assets/imgs/avatar.jpg" alt="" />
|
||||
</el-avatar>
|
||||
<div>
|
||||
<div class="text-14px">
|
||||
<Highlight :keys="item.keys.map((v) => t(v))">
|
||||
{{ item.type }} : {{ item.title }}
|
||||
</Highlight>
|
||||
</div>
|
||||
<div class="mt-16px text-12px text-gray-400">
|
||||
{{ formatTime(item.date, 'yyyy-MM-dd') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
</div>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { set } from 'lodash-es'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { formatTime } from '@/utils'
|
||||
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useWatermark } from '@/hooks/web/useWatermark'
|
||||
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
|
||||
import { pieOptions, barOptions } from './echarts-data'
|
||||
|
||||
defineOptions({ name: 'Home' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const userStore = useUserStore()
|
||||
const { setWatermark } = useWatermark()
|
||||
const loading = ref(true)
|
||||
const avatar = userStore.getUser.avatar
|
||||
const username = userStore.getUser.nickname
|
||||
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
||||
// 获取统计数
|
||||
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 notice = reactive<Notice[]>([])
|
||||
const getNotice = async () => {
|
||||
const data = [
|
||||
{
|
||||
title: '系统升级版本',
|
||||
type: '通知',
|
||||
keys: ['通知', '升级'],
|
||||
date: new Date()
|
||||
},
|
||||
{
|
||||
title: '系统凌晨维护',
|
||||
type: '公告',
|
||||
keys: ['公告', '维护'],
|
||||
date: new Date()
|
||||
},
|
||||
{
|
||||
title: '系统升级版本',
|
||||
type: '通知',
|
||||
keys: ['通知', '升级'],
|
||||
date: new Date()
|
||||
},
|
||||
{
|
||||
title: '系统凌晨维护',
|
||||
type: '公告',
|
||||
keys: ['公告', '维护'],
|
||||
date: new Date()
|
||||
}
|
||||
]
|
||||
notice = Object.assign(notice, data)
|
||||
}
|
||||
|
||||
// 获取快捷入口
|
||||
let shortcut = reactive<Shortcut[]>([])
|
||||
|
||||
const getShortcut = async () => {
|
||||
const data = [
|
||||
{
|
||||
name: 'Github',
|
||||
icon: 'akar-icons:github-fill',
|
||||
url: 'github.io'
|
||||
},
|
||||
{
|
||||
name: 'Vue',
|
||||
icon: 'logos:vue',
|
||||
url: 'vuejs.org'
|
||||
},
|
||||
{
|
||||
name: 'Vite',
|
||||
icon: 'vscode-icons:file-type-vite',
|
||||
url: 'https://vitejs.dev/'
|
||||
},
|
||||
{
|
||||
name: 'Angular',
|
||||
icon: 'logos:angular-icon',
|
||||
url: 'github.io'
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
icon: 'logos:react',
|
||||
url: 'github.io'
|
||||
},
|
||||
{
|
||||
name: 'Webpack',
|
||||
icon: 'logos:webpack',
|
||||
url: 'github.io'
|
||||
}
|
||||
]
|
||||
shortcut = Object.assign(shortcut, 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))
|
||||
)
|
||||
pieOptionsData!.series![0].data = data.map((v) => {
|
||||
return {
|
||||
name: t(v.name),
|
||||
value: v.value
|
||||
}
|
||||
})
|
||||
}
|
||||
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 getAllApi = async () => {
|
||||
await Promise.all([
|
||||
getCount(),
|
||||
getProject(),
|
||||
getNotice(),
|
||||
getShortcut(),
|
||||
getUserAccessSource(),
|
||||
getWeeklyUserActivity()
|
||||
])
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
getAllApi()
|
||||
</script>
|
||||
1800
src/views/Home/Index10.vue
Normal file
1800
src/views/Home/Index10.vue
Normal file
File diff suppressed because it is too large
Load Diff
466
src/views/Home/Index11.vue
Normal file
466
src/views/Home/Index11.vue
Normal file
@@ -0,0 +1,466 @@
|
||||
<template>
|
||||
<div class="w-100%" v-loading="loading">
|
||||
<div class="search-top flex justify-right">
|
||||
<!-- <div class="search-top flex justify-between"> -->
|
||||
<!-- 租户搜索 -->
|
||||
<!-- <div>
|
||||
<span class="text-13px c-#656565"> 租户名称:</span>
|
||||
<el-select
|
||||
v-model="zhName"
|
||||
placeholder="请选择"
|
||||
size="large"
|
||||
style="width: 264px"
|
||||
@change="selectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in zhOption"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div> -->
|
||||
<!-- 切换日期 -->
|
||||
<div class="flex">
|
||||
<div class="w-200px grid grid-cols-3 mr-20px items-center">
|
||||
<div
|
||||
class="text-13px text-center cursor-pointer"
|
||||
:style="{ color: btnActive == item.name ? '#692AFB' : '#656565' }"
|
||||
v-for="item in btnList"
|
||||
:key="item.type"
|
||||
@click="btnClick(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-date-picker
|
||||
v-model="pickerValue"
|
||||
type="daterange"
|
||||
range-separator="~"
|
||||
start-placeholder="Start date"
|
||||
end-placeholder="End date"
|
||||
value-format="YYYY-MM-DD"
|
||||
size="default"
|
||||
:editable="false"
|
||||
@change="pirckerChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-20px">
|
||||
<div
|
||||
class="text-13px c-#333333 font-700 bg-white h-45px pl-20px flex items-center b-1px b-solid b-#E4E4E4 b-t-0 b-l-0 b-r-0 .dark:b-#6B7280 .dark:c-#6B7280 .dark:bg-#1D1E1F"
|
||||
>
|
||||
登录次数/使用人数对比分析
|
||||
</div>
|
||||
<Echart :options="login_option" width="100%" height="423px" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-x-20px mt-30px">
|
||||
<div class="bg-#fff b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid .dark:bg-#1D1E1F">
|
||||
<div
|
||||
class="h-45px flex items-center text-13px c-#333333 font-700 pl-20px b-1px b-solid b-#E4E4E4 b-t-0 b-l-0 b-r-0 .dark:c-#6B7280 .dark:b-#6B7280"
|
||||
style="font-family: Arial-BoldMT, 'Arial Bold', Arial, sans-serif"
|
||||
>
|
||||
使用人数最多的模块top5
|
||||
</div>
|
||||
<div class="p-20px">
|
||||
<avue-crud :option="mostOption" :data="mostData"></avue-crud>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-#fff b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid">
|
||||
<div
|
||||
class="h-45px flex items-center text-13px c-#333333 font-700 pl-20px b-1px b-solid b-#E4E4E4 b-t-0 b-l-0 b-r-0 .dark:c-#6B7280 .dark:b-#6B7280"
|
||||
style="font-family: Arial-BoldMT, 'Arial Bold', Arial, sans-serif"
|
||||
>
|
||||
使用人数最少的模块bottom5
|
||||
</div>
|
||||
<div class="p-20px">
|
||||
<avue-crud :option="mostOption" :data="leastData"></avue-crud>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { getTableList, batchGetTableList } from '@/api/design/report'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import Icon from '@/components/Icon/src/Icon.vue'
|
||||
|
||||
defineOptions({ name: 'Home11' })
|
||||
|
||||
interface LoginXdata {
|
||||
value: string | number
|
||||
textStyle: {
|
||||
color: string
|
||||
fontSize: number
|
||||
}
|
||||
}
|
||||
interface TabData {
|
||||
mk: string
|
||||
pm: number
|
||||
syyhs: number
|
||||
zzf: string
|
||||
zzfFlag: number
|
||||
}
|
||||
|
||||
const cardCellColor = ref('rgb(0 0 0 / 64.7%)')
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const zhName = ref<string>('')
|
||||
// 租户字典
|
||||
const zhOption = ref([
|
||||
// {
|
||||
// value: 'Option1',
|
||||
// label: 'Option1'
|
||||
// }
|
||||
])
|
||||
// 租户字典的change
|
||||
const selectChange = (val) => {
|
||||
}
|
||||
|
||||
// 获取日期
|
||||
let now = new Date()
|
||||
const pickerValue = ref<Date | [Date, Date] | [string, string]>()
|
||||
const dayArr = ref<[string, string]>()
|
||||
const weekArr = ref<[string, string]>()
|
||||
const monthArr = ref<[string, string]>()
|
||||
const getDay = () => {
|
||||
let day = formatDate(now, 'YYYY-MM-DD')
|
||||
dayArr.value = [day, day]
|
||||
}
|
||||
const getWeek = () => {
|
||||
var firstDayOfWeek = new Date(now)
|
||||
firstDayOfWeek.setDate(firstDayOfWeek.getDate() - firstDayOfWeek.getDay() + 1)
|
||||
var lastDayOfWeek = new Date(firstDayOfWeek)
|
||||
lastDayOfWeek.setDate(firstDayOfWeek.getDate() + 6)
|
||||
var first =
|
||||
firstDayOfWeek.getFullYear() +
|
||||
'-' +
|
||||
String(firstDayOfWeek.getMonth() + 1).padStart(2, '0') +
|
||||
'-' +
|
||||
String(firstDayOfWeek.getDate()).padStart(2, '0')
|
||||
var last =
|
||||
lastDayOfWeek.getFullYear() +
|
||||
'-' +
|
||||
String(lastDayOfWeek.getMonth() + 1).padStart(2, '0') +
|
||||
'-' +
|
||||
String(lastDayOfWeek.getDate()).padStart(2, '0')
|
||||
|
||||
weekArr.value = [first, last]
|
||||
}
|
||||
const getMonth = () => {
|
||||
let month = now.getMonth()
|
||||
let year = now.getFullYear()
|
||||
var first = formatDate(new Date(year, month, 1), 'YYYY-MM-DD')
|
||||
var last = formatDate(new Date(year, month + 1, 0), 'YYYY-MM-DD')
|
||||
monthArr.value = [first, last]
|
||||
}
|
||||
|
||||
// 切换日期
|
||||
const btnList = ref([
|
||||
{ name: '本日', type: 1 },
|
||||
{ name: '本周', type: 2 },
|
||||
{ name: '本月', type: 3 }
|
||||
])
|
||||
const btnActive = ref('本月')
|
||||
const btnClick = (val) => {
|
||||
btnActive.value = val.name
|
||||
if (val.type == 1) {
|
||||
pickerValue.value = dayArr.value
|
||||
} else if (val.type == 2) {
|
||||
pickerValue.value = weekArr.value
|
||||
} else if (val.type == 3) {
|
||||
pickerValue.value = monthArr.value
|
||||
}
|
||||
}
|
||||
|
||||
const pirckerChange = (val) => {
|
||||
btnActive.value = ''
|
||||
}
|
||||
|
||||
// 登录分析柱形图
|
||||
const loginSeries1 = ref<number[]>([])
|
||||
const loginSeries2 = ref<number[]>([])
|
||||
const loginX = ref<any>([])
|
||||
const loginXutil = ref({
|
||||
color: 'rgba(0, 0, 0, 0.647058823529412)',
|
||||
fontSize: 14
|
||||
})
|
||||
const loginXdata = ref<LoginXdata[]>([])
|
||||
|
||||
const login_option = ref({
|
||||
backgroundColor: 'white',
|
||||
grid: { left: '3%', right: '4%', bottom: '10%', containLabel: true },
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '3%',
|
||||
itemGap: 20,
|
||||
itemWidth: 34,
|
||||
itemHeight: 13,
|
||||
textStyle: {
|
||||
color: '#656565'
|
||||
},
|
||||
data: ['登录次数', '使用人数']
|
||||
},
|
||||
xAxis: {
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
},
|
||||
data: loginXdata.value
|
||||
},
|
||||
yAxis: {
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#E8E8E8'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '登录次数',
|
||||
type: 'bar',
|
||||
color: 'rgba(105, 42, 251, 1)',
|
||||
itemStyle: {
|
||||
borderRadius: [25, 25, 0, 0]
|
||||
},
|
||||
barGap: 0,
|
||||
data: loginSeries1.value
|
||||
},
|
||||
{
|
||||
name: '使用人数',
|
||||
type: 'bar',
|
||||
color: 'rgba(54, 203, 203, 0.996078431372549)',
|
||||
itemStyle: {
|
||||
borderRadius: [25, 25, 0, 0]
|
||||
},
|
||||
data: loginSeries2.value
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const mostOption = ref({
|
||||
menu: false,
|
||||
header: false,
|
||||
border: false,
|
||||
height: 350,
|
||||
column: [
|
||||
{
|
||||
// headerAlign: 'right',
|
||||
label: '排名',
|
||||
prop: 'pm'
|
||||
},
|
||||
{
|
||||
// headerAlign: 'left',
|
||||
label: '模块',
|
||||
prop: 'mk'
|
||||
},
|
||||
{
|
||||
// headerAlign: 'left',
|
||||
label: '使用用户数',
|
||||
prop: 'syyhs'
|
||||
},
|
||||
{
|
||||
label: '模块使用数',
|
||||
prop: 'mksys'
|
||||
}
|
||||
// {
|
||||
// headerAlign: 'right',
|
||||
// label: '周涨幅',
|
||||
// prop: 'zzf',
|
||||
// render: ({ row }) => {
|
||||
// let icon
|
||||
// if (row.zzfFlag == 1) {
|
||||
// icon = h(Icon, { icon: 'ep:top', size: 20, color: '#00A854' })
|
||||
// } else {
|
||||
// icon = h(Icon, { icon: 'ep:bottom', size: 20, color: '#F04134' })
|
||||
// }
|
||||
|
||||
// return h(
|
||||
// 'div',
|
||||
// {
|
||||
// style: { display: 'flex', alignItems: 'center', justifyContent: 'right' }
|
||||
// },
|
||||
// [h('span', { style: { marginRight: '5px' } }, row.zzf), icon]
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
]
|
||||
})
|
||||
|
||||
const mostData = ref<TabData[]>()
|
||||
const leastData = ref<TabData[]>()
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const init = async () => {
|
||||
let oneres = await batchGetTableList('example_sytj_syzdmk,example_sytj_syzsmk')
|
||||
mostData.value = oneres.example_sytj_syzdmk.records
|
||||
leastData.value = oneres.example_sytj_syzsmk.records
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
getDay()
|
||||
getWeek()
|
||||
getMonth()
|
||||
let item = btnList.value.find((r) => r.name == btnActive.value)
|
||||
if (item) {
|
||||
let arr = [dayArr.value, weekArr.value, monthArr.value]
|
||||
pickerValue.value = arr[item.type - 1]
|
||||
} else {
|
||||
pickerValue.value = monthArr.value
|
||||
}
|
||||
|
||||
await init()
|
||||
|
||||
// 判断是否为暗色模式
|
||||
const { wsCache } = useCache()
|
||||
if (wsCache.get(CACHE_KEY.IS_DARK)) {
|
||||
cardCellColor.value = ''
|
||||
login_option.value.backgroundColor = '#1D1E1F'
|
||||
login_option.value.legend.textStyle.color = '#6B7280'
|
||||
|
||||
loginXdata.value.forEach((ele) => {
|
||||
ele.textStyle.color = '#6B7280'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => appStore.isDark,
|
||||
(val) => {
|
||||
if (val) {
|
||||
cardCellColor.value = ''
|
||||
login_option.value.backgroundColor = '#1D1E1F'
|
||||
login_option.value.legend.textStyle.color = '#6B7280'
|
||||
loginXdata.value.forEach((ele) => {
|
||||
ele.textStyle.color = '#6B7280'
|
||||
})
|
||||
} else {
|
||||
cardCellColor.value = 'rgb(0 0 0 / 64.7%)'
|
||||
login_option.value.backgroundColor = 'white'
|
||||
login_option.value.legend.textStyle.color = '#656565'
|
||||
loginXdata.value.forEach((ele) => {
|
||||
ele.textStyle.color = 'rgba(0, 0, 0, 0.647058823529412)'
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => pickerValue.value,
|
||||
async (val) => {
|
||||
if (!val) return
|
||||
let date = val?.[0] + ',' + val?.[1]
|
||||
loading.value = true
|
||||
let res = await getTableList('example_sytj_dlsyrs', { sj: date })
|
||||
|
||||
let dlsyrs = res.records
|
||||
if (dlsyrs && dlsyrs.length > 0) {
|
||||
loginXdata.value = []
|
||||
loginX.value = []
|
||||
loginSeries1.value = []
|
||||
loginSeries2.value = []
|
||||
dlsyrs.forEach((ele) => {
|
||||
loginX.value.push(ele.sj)
|
||||
loginSeries1.value.push(ele.login_num)
|
||||
loginSeries2.value.push(ele.users_num)
|
||||
})
|
||||
|
||||
loginX.value.forEach((ele) => {
|
||||
if (val?.[0] == val?.[1]) {
|
||||
loginXdata.value.push({
|
||||
value: ele + ':00',
|
||||
textStyle: loginXutil.value
|
||||
})
|
||||
} else {
|
||||
loginXdata.value.push({
|
||||
value: ele,
|
||||
textStyle: loginXutil.value
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
login_option.value.xAxis.data = loginXdata.value
|
||||
login_option.value.series[0].data = loginSeries1.value
|
||||
login_option.value.series[1].data = loginSeries2.value
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
h5,
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-top {
|
||||
::v-deep(.el-select) {
|
||||
.el-select__wrapper {
|
||||
min-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-date-editor) {
|
||||
width: 280px;
|
||||
|
||||
.el-range__icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-range-input {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.el-range-separator {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.avue-crud) {
|
||||
.el-form {
|
||||
.el-table__inner-wrapper {
|
||||
.el-table__header-wrapper {
|
||||
.el-table__cell {
|
||||
height: 57px;
|
||||
|
||||
.cell {
|
||||
font-family: 'Microsoft Tai Le Bold', 'Microsoft Tai Le Regular', 'Microsoft Tai Le',
|
||||
sans-serif;
|
||||
color: v-bind('cardCellColor');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__body {
|
||||
.el-table__row {
|
||||
height: 57px;
|
||||
|
||||
&:last-child {
|
||||
.el-table__cell {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
319
src/views/Home/Index2.vue
Normal file
319
src/views/Home/Index2.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<el-row :class="prefixCls" :gutter="20" justify="space-between">
|
||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" :rows="2" animated>
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--peoples p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon :size="40" icon="svg-icon:peoples" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>{{ t('analysis.newUser') }}
|
||||
</div>
|
||||
<CountTo
|
||||
:duration="2600"
|
||||
:end-val="102400"
|
||||
:start-val="0"
|
||||
class="text-right text-20px font-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" :rows="2" animated>
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--message p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon :size="40" icon="svg-icon:message" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>{{ t('analysis.unreadInformation') }}
|
||||
</div>
|
||||
<CountTo
|
||||
:duration="2600"
|
||||
:end-val="81212"
|
||||
:start-val="0"
|
||||
class="text-right text-20px font-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" :rows="2" animated>
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon :size="40" icon="svg-icon:money" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>{{ t('analysis.transactionAmount') }}
|
||||
</div>
|
||||
<CountTo
|
||||
:duration="2600"
|
||||
:end-val="9280"
|
||||
:start-val="0"
|
||||
class="text-right text-20px font-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" :rows="2" animated>
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--shopping p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon :size="40" icon="svg-icon:shopping" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>{{ t('analysis.totalShopping') }}
|
||||
</div>
|
||||
<CountTo
|
||||
:duration="2600"
|
||||
:end-val="13600"
|
||||
:start-val="0"
|
||||
class="text-right text-20px font-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" justify="space-between">
|
||||
<el-col :lg="10" :md="24" :sm="24" :xl="10" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="300" :options="pieOptionsData" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :lg="14" :md="24" :sm="24" :xl="14" :xs="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="300" :options="barOptionsData" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-card class="mb-20px" shadow="hover">
|
||||
<el-skeleton :loading="loading" :rows="4" animated>
|
||||
<Echart :height="350" :options="lineOptionsData" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { set } from 'lodash-es'
|
||||
import { EChartsOption } from 'echarts'
|
||||
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import type { AnalysisTotalTypes } from './types'
|
||||
import { barOptions, lineOptions, pieOptions } from './echarts-data'
|
||||
|
||||
defineOptions({ name: 'Home2' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(true)
|
||||
const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('panel')
|
||||
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
||||
|
||||
let totalState = reactive<AnalysisTotalTypes>({
|
||||
users: 0,
|
||||
messages: 0,
|
||||
moneys: 0,
|
||||
shoppings: 0
|
||||
})
|
||||
|
||||
const getCount = async () => {
|
||||
const data = {
|
||||
users: 102400,
|
||||
messages: 81212,
|
||||
moneys: 9280,
|
||||
shoppings: 13600
|
||||
}
|
||||
totalState = Object.assign(totalState, data)
|
||||
}
|
||||
|
||||
// 用户来源
|
||||
const getUserAccessSource = async () => {
|
||||
const data = [
|
||||
{ value: 335, name: 'analysis.directAccess' },
|
||||
{ value: 310, name: 'analysis.mailMarketing' },
|
||||
{ value: 234, name: 'analysis.allianceAdvertising' },
|
||||
{ value: 135, name: 'analysis.videoAdvertising' },
|
||||
{ value: 1548, name: 'analysis.searchEngines' }
|
||||
]
|
||||
set(
|
||||
pieOptionsData,
|
||||
'legend.data',
|
||||
data.map((v) => t(v.name))
|
||||
)
|
||||
set(pieOptionsData, 'series.data', data)
|
||||
}
|
||||
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
|
||||
|
||||
// 周活跃量
|
||||
const getWeeklyUserActivity = async () => {
|
||||
const data = [
|
||||
{ value: 13253, name: 'analysis.monday' },
|
||||
{ value: 34235, name: 'analysis.tuesday' },
|
||||
{ value: 26321, name: 'analysis.wednesday' },
|
||||
{ value: 12340, name: 'analysis.thursday' },
|
||||
{ value: 24643, name: 'analysis.friday' },
|
||||
{ value: 1322, name: 'analysis.saturday' },
|
||||
{ value: 1324, name: 'analysis.sunday' }
|
||||
]
|
||||
set(
|
||||
barOptionsData,
|
||||
'xAxis.data',
|
||||
data.map((v) => t(v.name))
|
||||
)
|
||||
set(barOptionsData, 'series', [
|
||||
{
|
||||
name: t('analysis.activeQuantity'),
|
||||
data: data.map((v) => v.value),
|
||||
type: 'bar'
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
const lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption
|
||||
|
||||
// 每月销售总额
|
||||
const getMonthlySales = async () => {
|
||||
const data = [
|
||||
{ estimate: 100, actual: 120, name: 'analysis.january' },
|
||||
{ estimate: 120, actual: 82, name: 'analysis.february' },
|
||||
{ estimate: 161, actual: 91, name: 'analysis.march' },
|
||||
{ estimate: 134, actual: 154, name: 'analysis.april' },
|
||||
{ estimate: 105, actual: 162, name: 'analysis.may' },
|
||||
{ estimate: 160, actual: 140, name: 'analysis.june' },
|
||||
{ estimate: 165, actual: 145, name: 'analysis.july' },
|
||||
{ estimate: 114, actual: 250, name: 'analysis.august' },
|
||||
{ estimate: 163, actual: 134, name: 'analysis.september' },
|
||||
{ estimate: 185, actual: 56, name: 'analysis.october' },
|
||||
{ estimate: 118, actual: 99, name: 'analysis.november' },
|
||||
{ estimate: 123, actual: 123, name: 'analysis.december' }
|
||||
]
|
||||
set(
|
||||
lineOptionsData,
|
||||
'xAxis.data',
|
||||
data.map((v) => t(v.name))
|
||||
)
|
||||
set(lineOptionsData, 'series', [
|
||||
{
|
||||
name: t('analysis.estimate'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: data.map((v) => v.estimate),
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: t('analysis.actual'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {},
|
||||
data: data.map((v) => v.actual),
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
const getAllApi = async () => {
|
||||
await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
getAllApi()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$prefix-cls: #{$namespace}-panel;
|
||||
|
||||
.#{$prefix-cls} {
|
||||
&__item {
|
||||
&--peoples {
|
||||
color: #40c9c6;
|
||||
}
|
||||
|
||||
&--message {
|
||||
color: #36a3f7;
|
||||
}
|
||||
|
||||
&--money {
|
||||
color: #f4516c;
|
||||
}
|
||||
|
||||
&--shopping {
|
||||
color: #34bfa3;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
:deep(.#{$namespace}-icon) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__item--icon {
|
||||
transition: all 0.38s ease-out;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__item--peoples {
|
||||
background: #40c9c6;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__item--message {
|
||||
background: #36a3f7;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__item--money {
|
||||
background: #f4516c;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__item--shopping {
|
||||
background: #34bfa3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
347
src/views/Home/Index3.vue
Normal file
347
src/views/Home/Index3.vue
Normal file
@@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<div class="w-100%">
|
||||
<div class="flex justify-between items-center">
|
||||
<div
|
||||
class="w-[calc(50%-50px)] py-16px px-20px bg-#fff border-rd-10px b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
|
||||
>
|
||||
<h5 class="text">服务器基本信息</h5>
|
||||
<div class="w-100% inline-grid grid-cols-4 gap-4 text-center">
|
||||
<div class="left-bottom-box" v-for="(item, index) in topLeftData" :key="index">
|
||||
<p>{{ item.title }}</p>
|
||||
<div>
|
||||
<span>{{ item.num }}</span> {{ item.dw }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex w-[calc(50%-50px)] px-20px bg-#fff border-rd-10px justify-between items-center b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
|
||||
>
|
||||
<div class="conter-right-box" v-for="(item, index) in arr" :key="index">
|
||||
<p class="text-conter">{{ item.title }}</p>
|
||||
<div class="chart">
|
||||
<Echart :options="syl_pie_option" width="140px" height="130px"></Echart>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="conter-right-box">
|
||||
<p class="text-conter">内存使用率</p>
|
||||
<div class="chart">
|
||||
<Echart :options="syl_pie_option" width="140px" height="130px"></Echart>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conter-right-box">
|
||||
<p class="text-conter">系统平均负载(1m)</p>
|
||||
<div class="chart">
|
||||
<Echart :options="syl_pie_option" width="140px" height="130px"></Echart>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-20px">
|
||||
<div
|
||||
class="w-[calc(50%-50px)] bg-#fff border-rd-10px p-20px b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
|
||||
>
|
||||
<Echart :options="cpu_line_option" width="100%" height="350px" />
|
||||
</div>
|
||||
<div
|
||||
class="w-[calc(50%-50px)] bg-#fff border-rd-10px p-20px b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
|
||||
>
|
||||
<Echart :options="nc_line_option" width="100%" height="350px" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-20px">
|
||||
<div
|
||||
class="w-[calc(50%-50px)] bg-#fff border-rd-10px p-20px b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
|
||||
>
|
||||
<Echart :options="ll_line_option" width="100%" height="350px" />
|
||||
</div>
|
||||
<div
|
||||
class="w-[calc(50%-50px)] bg-#fff border-rd-10px p-20px b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
|
||||
>
|
||||
<Echart :options="cp_line_option" width="100%" height="350px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { batchGetTableList } from '@/api/design/report'
|
||||
|
||||
defineOptions({ name: 'Home3' })
|
||||
|
||||
interface TopLeftData {
|
||||
title: string
|
||||
dw: string
|
||||
dictionary: string
|
||||
num?: number
|
||||
}
|
||||
|
||||
const topLeftData = ref<TopLeftData[]>([
|
||||
{ title: '系统运行时间', dw: '天', dictionary: 'xtyxsj' },
|
||||
{ title: 'cpu核心数', dw: '', dictionary: 'cpuhxs' },
|
||||
{ title: '内存总量', dw: 'G', dictionary: 'nczl' },
|
||||
{ title: '系统平均负数', dw: '', dictionary: 'xtpjfz' }
|
||||
])
|
||||
|
||||
const arr = ref<any>([
|
||||
{ title: 'CPU使用率(1m)', dictionary: 'cpusyl' },
|
||||
{ title: '内存使用率', dictionary: 'ncsyl' },
|
||||
{ title: '系统平均负载(1m)', dictionary: 'xtpjfzl' }
|
||||
])
|
||||
|
||||
const syl_pie_option = ref<any>({
|
||||
title: { text: '50%', left: 'center', top: 'center' },
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
stillShowZeroSum: false,
|
||||
label: { show: false },
|
||||
hoverAnimation: false,
|
||||
data: [
|
||||
{ value: 100, name: 'A' },
|
||||
{ value: 0, name: 'B' }
|
||||
],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: function (params) {
|
||||
var colors = ['#e9e9e9', '#0099ff', '#51d351']
|
||||
return colors[params.dataIndex % colors.length]
|
||||
}
|
||||
}
|
||||
},
|
||||
radius: ['50%', '70%']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const cpuLineXAxis = ref<string[]>([])
|
||||
const cpuLineSeries = ref<string[]>([])
|
||||
const cpu_line_option = reactive<any>({
|
||||
title: {
|
||||
text: 'CPU使用率',
|
||||
textStyle: {}
|
||||
},
|
||||
tooltip: {},
|
||||
legend: { data: ['使用率'], x: 'right', selectedMode: false },
|
||||
xAxis: {
|
||||
data: cpuLineXAxis.value
|
||||
},
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{
|
||||
name: '使用率',
|
||||
data: cpuLineSeries.value,
|
||||
type: 'line',
|
||||
stack: 'x',
|
||||
areaStyle: { color: '#ffd7dc' },
|
||||
itemStyle: { normal: { color: '#ffd7dc', lineStyle: { color: '#ff7a8c' } } }
|
||||
}
|
||||
]
|
||||
})
|
||||
const ncLineXAxis = ref<string[]>([])
|
||||
const ncLineSeries = ref<string[]>([])
|
||||
const nc_line_option = reactive<any>({
|
||||
title: {
|
||||
text: '内存使用率',
|
||||
textStyle: {}
|
||||
},
|
||||
tooltip: {},
|
||||
legend: { data: ['使用率'], x: 'right', selectedMode: false },
|
||||
xAxis: {
|
||||
data: ncLineXAxis.value
|
||||
},
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{
|
||||
name: '使用率',
|
||||
data: ncLineSeries.value,
|
||||
type: 'line',
|
||||
stack: 'x',
|
||||
areaStyle: { color: '#b2e0ff' },
|
||||
itemStyle: { normal: { color: '#b2e0ff', lineStyle: { color: '#0099ff' } } }
|
||||
}
|
||||
]
|
||||
})
|
||||
const llLineXAxis = ref<string[]>([])
|
||||
const llLineSeries1 = ref<string[]>([])
|
||||
const llLineSeries2 = ref<string[]>([])
|
||||
const ll_line_option = reactive<any>({
|
||||
title: {
|
||||
text: '服务器流量',
|
||||
textStyle: {}
|
||||
},
|
||||
tooltip: {},
|
||||
legend: { data: ['上传', '下载'], x: 'right', selectedMode: false },
|
||||
xAxis: { data: llLineXAxis.value },
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{
|
||||
name: '上传',
|
||||
data: llLineSeries1.value,
|
||||
type: 'line',
|
||||
// stack: 'x',
|
||||
areaStyle: { color: '#caf1ca' },
|
||||
itemStyle: { normal: { color: '#51d351', lineStyle: { color: '#51d351' } } }
|
||||
},
|
||||
{
|
||||
name: '下载',
|
||||
data: llLineSeries2.value,
|
||||
type: 'line',
|
||||
// stack: 'x',
|
||||
areaStyle: { color: '#ffe5c9' },
|
||||
itemStyle: { normal: { color: '#ffb465', lineStyle: { color: '#ffb465' } } }
|
||||
}
|
||||
]
|
||||
})
|
||||
const cpLineXAxis = ref<string[]>([])
|
||||
const cpLineSeries1 = ref<string[]>([])
|
||||
const cpLineSeries2 = ref<string[]>([])
|
||||
const cp_line_option = reactive<any>({
|
||||
title: {
|
||||
text: '服务器磁盘IO',
|
||||
textStyle: {}
|
||||
},
|
||||
tooltip: {},
|
||||
legend: { data: ['输出', '输入'], x: 'right', selectedMode: false },
|
||||
xAxis: { data: cpLineXAxis.value },
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{
|
||||
name: '输出',
|
||||
data: cpLineSeries1.value,
|
||||
type: 'line',
|
||||
// stack: 'x',
|
||||
areaStyle: { color: '#bdf1f1' },
|
||||
itemStyle: { normal: { color: '#78e3e4', lineStyle: { color: '#78e3e4' } } }
|
||||
},
|
||||
{
|
||||
name: '输入',
|
||||
data: cpLineSeries2.value,
|
||||
type: 'line',
|
||||
// stack: 'x',
|
||||
areaStyle: { color: '#d9d1fc' },
|
||||
itemStyle: { normal: { color: '#8167f5', lineStyle: { color: '#8167f5' } } }
|
||||
}
|
||||
]
|
||||
})
|
||||
const textStyle = reactive({
|
||||
color: ''
|
||||
})
|
||||
const appStore = useAppStore()
|
||||
|
||||
const init = async () => {
|
||||
let oneres = await batchGetTableList(
|
||||
'example_systemmonitor_server_information,example_systemmonitor_cpu_utilization,example_systemmonitor_memory_utilization,example_systemmonitor_server_traffic,example_systemmonitor_disk_io'
|
||||
)
|
||||
|
||||
let serverInformation = oneres.example_systemmonitor_server_information.records[0]
|
||||
topLeftData.value = topLeftData.value.map((item) => {
|
||||
return (item = {
|
||||
...item,
|
||||
num: serverInformation[item.dictionary]
|
||||
})
|
||||
})
|
||||
arr.value = arr.value.map((item) => {
|
||||
syl_pie_option.value.title.text = serverInformation[item.dictionary] + '%'
|
||||
syl_pie_option.value.series[0].data[1].value = serverInformation[item.dictionary]
|
||||
return item
|
||||
})
|
||||
//CPU使用率
|
||||
let cpuUtilization = oneres.example_systemmonitor_cpu_utilization.records
|
||||
cpuUtilization.forEach((ele) => {
|
||||
cpuLineXAxis.value.push(ele.sj)
|
||||
cpuLineSeries.value.push(ele.syl)
|
||||
})
|
||||
//内存使用率
|
||||
let memoryUtilization = oneres.example_systemmonitor_memory_utilization.records
|
||||
memoryUtilization.forEach((ele) => {
|
||||
ncLineXAxis.value.push(ele.sj)
|
||||
ncLineSeries.value.push(ele.syl)
|
||||
})
|
||||
//服务器流量
|
||||
let serverTraffic = oneres.example_systemmonitor_server_traffic.records
|
||||
serverTraffic.forEach((ele) => {
|
||||
llLineXAxis.value.push(ele.sj)
|
||||
llLineSeries1.value.push(ele.sc)
|
||||
llLineSeries2.value.push(ele.xz)
|
||||
})
|
||||
//服务器磁盘IO
|
||||
let diskIo = oneres.example_systemmonitor_disk_io.records
|
||||
diskIo.forEach((ele) => {
|
||||
cpLineXAxis.value.push(ele.sj)
|
||||
cpLineSeries1.value.push(ele.sc)
|
||||
cpLineSeries2.value.push(ele.sr)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
// 判断是否为暗色模式
|
||||
const { wsCache } = useCache()
|
||||
if (wsCache.get(CACHE_KEY.IS_DARK)) {
|
||||
textStyle.color = '#E5EAF3'
|
||||
cpu_line_option.title.textStyle = textStyle
|
||||
nc_line_option.title.textStyle = textStyle
|
||||
ll_line_option.title.textStyle = textStyle
|
||||
cp_line_option.title.textStyle = textStyle
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => appStore.isDark,
|
||||
(val) => {
|
||||
if (val) {
|
||||
textStyle.color = '#E5EAF3'
|
||||
} else {
|
||||
textStyle.color = 'black'
|
||||
}
|
||||
cpu_line_option.title.textStyle = textStyle
|
||||
nc_line_option.title.textStyle = textStyle
|
||||
ll_line_option.title.textStyle = textStyle
|
||||
cp_line_option.title.textStyle = textStyle
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
h5,
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.conter-right-box {
|
||||
width: 200px;
|
||||
text-align: center;
|
||||
|
||||
.text-conter {
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chart {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.left-bottom-box {
|
||||
margin: 20px 0;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
601
src/views/Home/Index4.vue
Normal file
601
src/views/Home/Index4.vue
Normal file
@@ -0,0 +1,601 @@
|
||||
<template>
|
||||
<div class="w-100%">
|
||||
<div class="w-100% flex gap-x-20px">
|
||||
<div class="el-card top-card">
|
||||
<div class="card-box">
|
||||
<div class="text text-gray-500">月销售金额(元)</div>
|
||||
<Icon color="#bdbdbd" class="cursor-pointer" icon="quill:warning-alt" />
|
||||
</div>
|
||||
<span class="text-value">
|
||||
<avue-count-up :end="topList?.yxsje.value"></avue-count-up>
|
||||
</span>
|
||||
<div class="card-box mt-20px">
|
||||
<div class="text">
|
||||
<span class="mr-5px text-gray-500">周同比</span>
|
||||
<Icon
|
||||
v-if="topList?.yxsje.ztbType == 'top'"
|
||||
:size="12"
|
||||
color="#F56C6C"
|
||||
icon="ep:caret-top"
|
||||
></Icon>
|
||||
<Icon
|
||||
v-else-if="topList?.yxsje.ztbType == 'bottom'"
|
||||
:size="12"
|
||||
color="#19BE6B"
|
||||
icon="ep:caret-bottom"
|
||||
></Icon>
|
||||
<span>{{ topList?.yxsje.ztb }}</span>
|
||||
</div>
|
||||
<div class="text">
|
||||
<span class="mr-5px text-gray-500">日环比</span>
|
||||
<Icon
|
||||
v-if="topList?.yxsje.rhbType == 'top'"
|
||||
:size="12"
|
||||
color="#F56C6C"
|
||||
icon="ep:caret-top"
|
||||
></Icon>
|
||||
<Icon
|
||||
v-else-if="topList?.yxsje.rhbType == 'bottom'"
|
||||
:size="12"
|
||||
color="#19BE6B"
|
||||
icon="ep:caret-bottom"
|
||||
></Icon>
|
||||
<span>{{ topList?.yxsje.rhb }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="el-card top-card">
|
||||
<div class="card-box">
|
||||
<div class="text text-gray-500">月回款金额(元)</div>
|
||||
<Icon color="#bdbdbd" class="cursor-pointer" icon="quill:warning-alt" />
|
||||
</div>
|
||||
<span class="text-value">
|
||||
<avue-count-up :end="topList?.yhkje.value"></avue-count-up>
|
||||
</span>
|
||||
<div class="card-box w-[calc(100%+20px)]! ml--10px">
|
||||
<div class="pos-relative w-100%">
|
||||
<Echart
|
||||
class="w-100% mt-10px pos-absolute left-0 top--30px"
|
||||
:options="returned_money_option"
|
||||
width="100%"
|
||||
height="60px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="el-card top-card">
|
||||
<div class="card-box">
|
||||
<div class="text text-gray-500">本月成交订单</div>
|
||||
<Icon color="#bdbdbd" class="cursor-pointer" icon="quill:warning-alt" />
|
||||
</div>
|
||||
<span class="text-value">
|
||||
<avue-count-up :end="topList?.bycjdd.value"></avue-count-up>
|
||||
</span>
|
||||
<div class="card-box w-[calc(100%+20px)]! ml--10px">
|
||||
<div class="pos-relative w-100%">
|
||||
<Echart
|
||||
class="w-100% mt-10px pos-absolute left-0 top--30px"
|
||||
:options="submit_order_option"
|
||||
width="100%"
|
||||
height="60px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="el-card top-card pos-relative">
|
||||
<div class="card-box">
|
||||
<div class="text text-gray-500">完成销售目标</div>
|
||||
<Icon class="cursor-pointer" color="#bdbdbd" icon="quill:warning-alt" />
|
||||
</div>
|
||||
<div class="text-value">{{ topList?.wcxsmb.value }}%</div>
|
||||
<div class="pos-absolute right-30px top-50px">
|
||||
<el-progress
|
||||
type="circle"
|
||||
:percentage="completeValue"
|
||||
:stroke-width="9"
|
||||
:width="60"
|
||||
:show-text="false"
|
||||
:indeterminate="true"
|
||||
></el-progress>
|
||||
</div>
|
||||
</div>
|
||||
<div class="el-card top-card">
|
||||
<div class="card-box">
|
||||
<div class="text text-gray-500">回款达成率</div>
|
||||
<Icon color="#bdbdbd" class="cursor-pointer" icon="quill:warning-alt" />
|
||||
</div>
|
||||
<div class="text-value mb-30px!">{{ topList?.hkdcl.value }}%</div>
|
||||
<el-progress
|
||||
:percentage="returnedValue"
|
||||
:stroke-width="9"
|
||||
:show-text="false"
|
||||
class="jdt"
|
||||
></el-progress>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-100% flex gap-x-20px">
|
||||
<div class="flex-grow-3 flex-shrink flex-basis-0">
|
||||
<el-card header="数据简报">
|
||||
<div class="data-bulletin grid grid-cols-4 gap-y-4 text-center py-10px">
|
||||
<template v-for="(item, index) in dataBulletin" :key="index">
|
||||
<div class="p-14px">
|
||||
<div class="text-gray-500 text-14px">{{ item.label }}</div>
|
||||
<div class="text-28px fw-bold c-#666 dark:c-[var(--el-text-color-primary)]">
|
||||
<avue-count-up :end="item.value"></avue-count-up>
|
||||
</div>
|
||||
<div :style="{ color: item.type == 'pos' ? '#19BE6B' : '#F56C6C' }"
|
||||
>{{ item.type == 'pos' ? '+' : '-' }}{{ item.percent }}</div
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card header="业绩目标">
|
||||
<Echart :options="target_bar_option" width="100%" height="350px" />
|
||||
</el-card>
|
||||
<el-card header="销售预测">
|
||||
<Echart :options="forecast_bar_option" width="100%" height="350px" />
|
||||
</el-card>
|
||||
<el-card header="销售漏斗">
|
||||
<div id="forecast-funnel-chart" class="w-100% h-350px"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div class="flex-grow-2 flex-shrink flex-basis-0">
|
||||
<el-card header="销售排名">
|
||||
<div class="ranking-box">
|
||||
<div class="flex items-center gap-x-10px text-left text-12px c-#999 px-14px mb-10px">
|
||||
<div v-for="(item, index) in rankingTopList" :key="index" :class="item.width">{{
|
||||
item.label
|
||||
}}</div>
|
||||
</div>
|
||||
<template v-for="(item, index) in rankingList" :key="index">
|
||||
<div
|
||||
class="flex items-center gap-x-10px text-left text-12px c-#666 p-14px b-0 b-b-1px b-[var(--el-card-border-color)] b-solid"
|
||||
>
|
||||
<div class="w-10% text-left">
|
||||
<div
|
||||
class="w-23px h-23px text-center line-height-23px bg-#ccc border-rd-4px c-#fff"
|
||||
:style="index < 3 ? { background: '#ff9900' } : ''"
|
||||
>{{ index + 1 }}</div
|
||||
>
|
||||
</div>
|
||||
<div class="w-20%">{{ item.name }}</div>
|
||||
<div class="w-25%">{{ item.money }}</div>
|
||||
<div class="w-45%">
|
||||
<el-progress
|
||||
:percentage="item.percent"
|
||||
:stroke-width="8"
|
||||
:color="rankingColor[index] || '#409EFF'"
|
||||
></el-progress>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card header="快捷菜单">
|
||||
<div class="fast-menu-box grid grid-cols-3 gap-y-20px c-#666 dark:c-#cfd3dc">
|
||||
<template v-for="(item, index) in fastMenuList" :key="index">
|
||||
<div
|
||||
class="flex justify-center items-center flex-col hover:bg-#f1f1f1 hover:b-#dedddd! hover:dark:bg-#1e1e1e cursor-pointer"
|
||||
>
|
||||
<div class="h-70px w-70px flex justify-center items-center">
|
||||
<Icon :icon="item.icon" :size="item.size"></Icon>
|
||||
</div>
|
||||
<div class="mt-4px text-12px">{{ item.label }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card header="系统通知">
|
||||
<div class="notifier-box">
|
||||
<template v-for="(item, index) in notifierList" :key="index">
|
||||
<div class="flex items-center py-12px px-10px">
|
||||
<div class="bg-#F56C6C w-4px h-4px border-rd-50% flex-shrink-0"></div>
|
||||
<div class="text-14px flex-1 ml-5px mr-20px">{{ item.label }}</div>
|
||||
<div class="text-gray-500 text-12px flex-shrink-0">{{ item.time }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as echarts from 'echarts'
|
||||
import { batchGetTableList } from '@/api/design/report'
|
||||
|
||||
defineOptions({ name: 'Home4' })
|
||||
|
||||
interface TopList {
|
||||
bycjdd: {
|
||||
data: number[]
|
||||
value: string
|
||||
}
|
||||
hkdcl: {
|
||||
value: number
|
||||
}
|
||||
wcxsmb: {
|
||||
value: number
|
||||
}
|
||||
yhkje: {
|
||||
data: number[]
|
||||
value: string
|
||||
}
|
||||
yxsje: {
|
||||
value: string
|
||||
rhb: string
|
||||
rhbType: string
|
||||
ztb: string
|
||||
ztbType: string
|
||||
}
|
||||
}
|
||||
interface DataBulletin {
|
||||
label: string
|
||||
dictionary: string
|
||||
value?: string
|
||||
percent?: string
|
||||
type?: string
|
||||
}
|
||||
interface RankingList {
|
||||
name: string
|
||||
money: string
|
||||
percent: number
|
||||
}
|
||||
interface RankingTopList {
|
||||
label: string
|
||||
width: string
|
||||
}
|
||||
interface SalesTarget {
|
||||
cjje: string
|
||||
sj: string
|
||||
mbje: string
|
||||
}
|
||||
interface SalesForecast {
|
||||
yjxsje: string
|
||||
sj: string
|
||||
glje: string
|
||||
}
|
||||
interface SalesFunnelgraphic {
|
||||
dictionary: string
|
||||
name: string
|
||||
value?: number
|
||||
}
|
||||
interface NotifierList {
|
||||
label: string
|
||||
time: string
|
||||
}
|
||||
|
||||
let topList = ref<TopList>()
|
||||
|
||||
let returned_money_option = ref({
|
||||
grid: { bottom: '60%', containLabel: false },
|
||||
xAxis: {
|
||||
show: false,
|
||||
type: 'category',
|
||||
boundaryGap: false
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: topList.value?.yhkje.data,
|
||||
type: 'line',
|
||||
symbol: 'none',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: '#FF7A8C'
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#FF7A8C'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
const submit_order_option = ref({
|
||||
grid: { bottom: '60%', containLabel: false },
|
||||
xAxis: {
|
||||
show: false,
|
||||
type: 'category'
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: topList.value?.bycjdd.data,
|
||||
type: 'bar',
|
||||
itemStyle: {
|
||||
color: '#52C1F5'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
const completeValue = ref(0)
|
||||
const returnedValue = ref(0)
|
||||
|
||||
const dataBulletin = ref<DataBulletin[]>([
|
||||
{ label: '新增客户', dictionary: 'xzkh' },
|
||||
{ label: '新增线索', dictionary: 'xzxs' },
|
||||
{ label: '新增商机', dictionary: 'xzsj' },
|
||||
{ label: '新增订单', dictionary: 'xzdd' },
|
||||
{ label: '新增联系人', dictionary: 'xnlxr' },
|
||||
{ label: '跟进次数', dictionary: 'gjcs' },
|
||||
{ label: '处理任务', dictionary: 'clrw' },
|
||||
{ label: '处理工单', dictionary: 'clgd' }
|
||||
])
|
||||
|
||||
const salesTarget = ref<SalesTarget[]>()
|
||||
let salesTargetXAxis = ref<string[]>([])
|
||||
let salesTargetSeries1 = ref<string[]>([])
|
||||
let salesTargetSeries2 = ref<string[]>([])
|
||||
const target_bar_option = ref({
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { data: ['目标金额', '成交金额'] },
|
||||
xAxis: {
|
||||
data: salesTargetXAxis.value
|
||||
},
|
||||
yAxis: {},
|
||||
series: [
|
||||
{
|
||||
name: '目标金额',
|
||||
type: 'bar',
|
||||
data: salesTargetSeries1.value,
|
||||
color: '#6CCAF6'
|
||||
},
|
||||
{
|
||||
name: '成交金额',
|
||||
type: 'bar',
|
||||
data: salesTargetSeries2.value,
|
||||
color: '#93D99A'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const salesForecast = ref<SalesForecast[]>()
|
||||
let salesForecastXAxis = ref<string[]>([])
|
||||
let salesForecastSeries1 = ref<string[]>([])
|
||||
let salesForecastSeries2 = ref<string[]>([])
|
||||
const forecast_bar_option = ref({
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { data: ['预计销售金额', '概率金额'] },
|
||||
xAxis: {
|
||||
data: salesForecastXAxis.value
|
||||
},
|
||||
yAxis: {},
|
||||
series: [
|
||||
{
|
||||
name: '预计销售金额',
|
||||
type: 'bar',
|
||||
data: salesForecastSeries1.value,
|
||||
color: '#FF8E9D'
|
||||
},
|
||||
{
|
||||
name: '概率金额',
|
||||
type: 'bar',
|
||||
data: salesForecastSeries2.value,
|
||||
color: '#88AEFB'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const salesFunnelgraphic = ref<SalesFunnelgraphic[]>([
|
||||
{ name: '初步洽谈', dictionary: 'cbqt' },
|
||||
{ name: '深入沟通', dictionary: 'srgt' },
|
||||
{ name: '产品报价', dictionary: 'cpbj' },
|
||||
{ name: '成交商机', dictionary: 'cjsj' },
|
||||
{ name: '流失商机', dictionary: 'lssj' }
|
||||
])
|
||||
const forecast_funnel_option = ref({
|
||||
title: {
|
||||
text: ''
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c}%'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Funnel',
|
||||
type: 'funnel',
|
||||
width: '90%',
|
||||
height: '100%',
|
||||
left: '5%',
|
||||
top: '3%',
|
||||
label: {
|
||||
position: 'right'
|
||||
},
|
||||
data: salesFunnelgraphic.value
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const rankingColor = ref(['#FB6260', '#FFA94C', '#4BCED0'])
|
||||
|
||||
const rankingTopList = ref<RankingTopList[]>([
|
||||
{
|
||||
label: '排名',
|
||||
width: 'w-10%'
|
||||
},
|
||||
{
|
||||
label: '姓名',
|
||||
width: 'w-20%'
|
||||
},
|
||||
{
|
||||
label: '销售金额',
|
||||
width: 'w-25%'
|
||||
},
|
||||
{
|
||||
label: '完成目标',
|
||||
width: 'w-45%'
|
||||
}
|
||||
])
|
||||
const rankingList = ref<RankingList[]>()
|
||||
|
||||
const fastMenuList = ref([
|
||||
{ label: '客户管理', size: 65, icon: 'la:user-tie' },
|
||||
{ label: '线索管理', size: 55, icon: 'akar-icons:light-bulb' },
|
||||
{ label: '商机管理', size: 55, icon: 'hugeicons:money-add-01' },
|
||||
{ label: '联系人管理', size: 45, icon: 'uiw:user' },
|
||||
{ label: '写新跟进', size: 55, icon: 'ph:pen' },
|
||||
{ label: '回款管理', size: 55, icon: 'fluent:calendar-reply-32-light' },
|
||||
{ label: '发票管理', size: 55, icon: 'hugeicons:invoice-01' },
|
||||
{ label: '费用管理', size: 50, icon: 'teenyicons:cost-estimate-outline' },
|
||||
{ label: '报销管理', size: 55, icon: 'hugeicons:money-bag-02' },
|
||||
{ label: '工作报告', size: 55, icon: 'line-md:document-report' },
|
||||
{ label: '工单管理', size: 60, icon: 'material-symbols-light:order-approve-outline-sharp' },
|
||||
{ label: '产品管理', size: 55, icon: 'fluent-mdl2:product' }
|
||||
])
|
||||
|
||||
const notifierList = ref<NotifierList[]>()
|
||||
|
||||
const init = async () => {
|
||||
let oneres = await batchGetTableList(
|
||||
'example_client_month_data,example_client_data_briefs,example_client_salesman_rank,example_client_sales_target'
|
||||
)
|
||||
|
||||
topList.value = oneres.example_client_month_data.records[0]
|
||||
returned_money_option.value.series[0].data = topList.value?.yhkje.data
|
||||
submit_order_option.value.series[0].data = topList.value?.bycjdd.data
|
||||
completeValue.value = topList.value?.wcxsmb.value || 0
|
||||
returnedValue.value = topList.value?.hkdcl.value || 0
|
||||
dataBulletin.value = dataBulletin.value.map((item) => {
|
||||
return (item = {
|
||||
...item,
|
||||
...oneres.example_client_data_briefs.records[0][item.dictionary]
|
||||
})
|
||||
})
|
||||
rankingList.value = oneres.example_client_salesman_rank.records
|
||||
salesTarget.value = oneres.example_client_sales_target.records
|
||||
salesTarget.value?.forEach((ele) => {
|
||||
salesTargetXAxis.value.push(ele.sj)
|
||||
salesTargetSeries1.value.push(ele.mbje)
|
||||
salesTargetSeries2.value.push(ele.cjje)
|
||||
})
|
||||
target_bar_option.value.xAxis.data = salesTargetXAxis.value
|
||||
target_bar_option.value.series[0].data = salesTargetSeries1.value
|
||||
target_bar_option.value.series[1].data = salesTargetSeries2.value
|
||||
|
||||
let endres = await batchGetTableList(
|
||||
'example_client_sales_forecast,example_client_sales_funnelgraphic,example_client_system_notification'
|
||||
)
|
||||
salesForecast.value = endres.example_client_sales_forecast.records
|
||||
salesForecast.value?.forEach((ele) => {
|
||||
salesForecastXAxis.value.push(ele.sj)
|
||||
salesForecastSeries1.value.push(ele.yjxsje)
|
||||
salesForecastSeries2.value.push(ele.glje)
|
||||
})
|
||||
forecast_bar_option.value.xAxis.data = salesForecastXAxis.value
|
||||
forecast_bar_option.value.series[0].data = salesForecastSeries1.value
|
||||
forecast_bar_option.value.series[1].data = salesForecastSeries2.value
|
||||
|
||||
salesFunnelgraphic.value = salesFunnelgraphic.value.map((item) => {
|
||||
return (item = {
|
||||
...item,
|
||||
value: endres.example_client_sales_funnelgraphic.records[0][item.dictionary]
|
||||
})
|
||||
})
|
||||
forecast_funnel_option.value.series[0].data = salesFunnelgraphic.value
|
||||
notifierList.value = endres.example_client_system_notification.records
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
let xsldId = document.getElementById('forecast-funnel-chart')
|
||||
if (xsldId) {
|
||||
let forecastFunnelChart = echarts.init(xsldId)
|
||||
forecastFunnelChart.setOption(forecast_funnel_option.value)
|
||||
}
|
||||
|
||||
const timer_1 = setInterval(() => {
|
||||
completeValue.value = completeValue.value + 10
|
||||
if (completeValue.value >= 75) {
|
||||
completeValue.value = 75
|
||||
clearInterval(timer_1)
|
||||
}
|
||||
}, 20)
|
||||
const timer_2 = setInterval(() => {
|
||||
returnedValue.value = returnedValue.value + 10
|
||||
if (returnedValue.value >= 80) {
|
||||
returnedValue.value = 80
|
||||
clearInterval(timer_2)
|
||||
}
|
||||
}, 20)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.top-card {
|
||||
position: relative;
|
||||
padding: 15px;
|
||||
margin-top: 0 !important;
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
flex: 1;
|
||||
|
||||
.card-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
|
||||
.text-value {
|
||||
margin-bottom: 15px;
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.text-value {
|
||||
margin-bottom: 15px;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-card) {
|
||||
margin-top: 20px;
|
||||
border-radius: 8px;
|
||||
|
||||
.el-card__header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
.data-bulletin {
|
||||
& > div {
|
||||
border-right: 1px solid var(--el-card-border-color);
|
||||
|
||||
&:nth-child(4n) {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ranking-box {
|
||||
.el-progress__text {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.fast-menu-box > div {
|
||||
padding: 10px 0;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.notifier-box > div {
|
||||
border-bottom: 1px solid var(--el-card-border-color);
|
||||
}
|
||||
|
||||
.notifier-box > div:nth-child(1) {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1102
src/views/Home/Index5.vue
Normal file
1102
src/views/Home/Index5.vue
Normal file
File diff suppressed because it is too large
Load Diff
913
src/views/Home/Index6.vue
Normal file
913
src/views/Home/Index6.vue
Normal file
@@ -0,0 +1,913 @@
|
||||
<template>
|
||||
<div class="w-100%">
|
||||
<div class="w-100% top-box">
|
||||
<div
|
||||
class="top-card bg-#fff .dark:bg-#1D1E1F! b-1px b-transparent .dark:b-#2A2B2C b-solid"
|
||||
v-for="(item, index) in topCardList"
|
||||
:class="item.border"
|
||||
:key="index"
|
||||
>
|
||||
<div class="card-box">
|
||||
<div class="title">{{ item.title }}</div>
|
||||
<Icon
|
||||
icon="akar-icons:info-fill"
|
||||
width="48"
|
||||
height="48"
|
||||
class="cursor-pointer"
|
||||
style="color: #bdbdbd"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-value .dark:c-#fff!">
|
||||
<span v-if="item.unit == '¥'">{{ item.unit }}</span>
|
||||
|
||||
<avue-count-up :end="item.num"></avue-count-up>
|
||||
<span v-if="item.unit == '家'" class="text-dw">{{ item.unit }}</span>
|
||||
</span>
|
||||
<div class="card-box mt-10px">
|
||||
<div class="" style="display: flex; align-items: center; height: 12px">
|
||||
<span v-if="item.type == 'pos'" class="icon">
|
||||
<Icon icon="ep:top" width="12" height="12" style="color: #f56c6c" />
|
||||
</span>
|
||||
<span v-else-if="item.type == 'neg'" class="icons">
|
||||
<Icon icon="ep:bottom" width="12" height="12" style="color: #19be6b" />
|
||||
</span>
|
||||
<span
|
||||
class="text-ts"
|
||||
:class="item.type == 'pos' ? 'text-zeng' : item.type == 'neg' ? 'text-jian' : ''"
|
||||
>{{ item.percentage }}</span
|
||||
>
|
||||
<span class="mr-5px text"> {{ item.tendency }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div class="content-left">
|
||||
<p class="content-title .dark:c-#fff!">商户外拓</p>
|
||||
<div class="jdt" v-for="(item, index) in jdtData" :key="index">
|
||||
<div class="jdt-top">
|
||||
<div style="display: flex; align-items: center" class="jdt-top-text">
|
||||
<span class="acitive" :class="'bj' + (index >= 3 ? 0 : index)"></span>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class=".dark:c-#fff!">
|
||||
<span class="num .dark:c-#fff!">{{ item.leftNum }}</span>
|
||||
<span class="num" style="font-size: 14px; font-weight: 400"
|
||||
>/{{ item.rightNum }}</span
|
||||
>
|
||||
<span class="jdt-top-text .dark:c-#fff!">{{ item.dw }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="jdt-bottom demo-progress">
|
||||
<el-progress
|
||||
:text-inside="true"
|
||||
:stroke-width="12"
|
||||
:percentage="item.jd"
|
||||
:status="item.type"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-center">
|
||||
<p class="content-title .dark:c-#fff!">商户分布</p>
|
||||
<div id="main" style="width: 380px; height: 300px"></div>
|
||||
</div>
|
||||
<div class="content-right">
|
||||
<p class="content-title .dark:c-#fff!">交易漏斗</p>
|
||||
<div id="ldMain" style="width: 380px; height: 280px"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eCharts">
|
||||
<div
|
||||
class="eCharts-left .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent .dark:c-#fff!"
|
||||
>
|
||||
<div class="eCharts-title .dark:c-#fff!">
|
||||
<p>日交易额</p>
|
||||
<p style="font-size: 14px" class="font">¥6,000,000</p>
|
||||
</div>
|
||||
<div id="zxtMain" style="width: 380px; height: 300px"></div>
|
||||
</div>
|
||||
<div class="eCharts-center .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div class="eCharts-title .dark:c-#fff!">
|
||||
<p>周订单量</p>
|
||||
<p style="font-size: 16px" class="font">10,000<span class="fontFamily">单</span></p>
|
||||
</div>
|
||||
<div id="zddlMain" style="width: 380px; height: 300px"></div>
|
||||
</div>
|
||||
<div
|
||||
class="eCharts-right .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent"
|
||||
style="position: relative"
|
||||
>
|
||||
<div style="position: absolute">
|
||||
<div class="eCharts-title .dark:c-#fff! w-100%">
|
||||
<p>开拓商家</p>
|
||||
<p style="margin-left: 235px; font-size: 14px">1,000家</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ktsjMain" style="width: 360px; height: 335px" class=".dark:c-#fff!"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eCharts-cjje .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div class="cjje-top">
|
||||
<div class="cjje-top-left .dark:c-#fff!">成交金额趋势</div>
|
||||
<div class="cjje-top-right">
|
||||
<el-radio-group v-model="tabPosition" style="margin-bottom: 30px">
|
||||
<el-radio-button
|
||||
v-for="(item, index) in cjjeTabList"
|
||||
:key="index"
|
||||
:value="item.label"
|
||||
@change="clickCjje(item.type)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cjjeMain" style="width: 1400px; height: 300px"></div>
|
||||
</div>
|
||||
<div
|
||||
class="eCharts-cjje .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent"
|
||||
style="padding-top: 0"
|
||||
>
|
||||
<div class="cjje-top">
|
||||
<div class="cjje-top-left .dark:c-#fff!" style="height: 60px; line-height: 60px">
|
||||
成交金额趋势
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Table></Table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as echarts from 'echarts'
|
||||
import Table from './components/table.vue'
|
||||
import { getTableList, batchGetTableList } from '@/api/design/report'
|
||||
|
||||
defineOptions({ name: 'Home6' })
|
||||
|
||||
interface TopCardList {
|
||||
dictionary: string
|
||||
border: string
|
||||
tendency: string
|
||||
title: string
|
||||
unit: string
|
||||
type?: string
|
||||
num?: string
|
||||
percentage?: string
|
||||
}
|
||||
|
||||
interface JdtData {
|
||||
dictionary: string
|
||||
title: string
|
||||
dw: string
|
||||
jd?: number
|
||||
type: any
|
||||
leftNum?: number
|
||||
rightNum?: number
|
||||
}
|
||||
|
||||
interface OptionSeriesData {
|
||||
name: string
|
||||
value: number
|
||||
}
|
||||
|
||||
type Ktsj = [string, string | number, string | number]
|
||||
|
||||
let topCardList = ref<TopCardList[]>([
|
||||
{ title: '商户总数', dictionary: 'shzs', tendency: '环比上周', border: '', unit: '家' },
|
||||
{ title: '开拓中商户', dictionary: 'ktzsh', tendency: '环比上周', border: '', unit: '家' },
|
||||
{ title: '总访问量', dictionary: 'zfwl', tendency: '环比上周', border: '', unit: '' },
|
||||
{ title: '总交易额', dictionary: 'zjye', tendency: '环比上周', border: '', unit: '¥' },
|
||||
{ title: '总订单量', dictionary: 'zddl', tendency: '环比上周', border: '', unit: '' },
|
||||
{ title: '客单价', dictionary: 'kdj', tendency: '环比上周', border: '', unit: '¥' }
|
||||
])
|
||||
|
||||
const jdtData = ref<JdtData[]>([
|
||||
{ title: '已计划', dictionary: 'yjh', dw: '家', type: '' },
|
||||
{ title: '已完成', dictionary: 'ywc', dw: '家', type: 'exception' },
|
||||
{ title: '开拓中', dictionary: 'ktz', dw: '家', type: 'warning' },
|
||||
{ title: '洽谈中', dictionary: 'qtz', dw: '家', type: '' }
|
||||
])
|
||||
|
||||
//环形图
|
||||
let currentIndex = ref(0) // 当前高亮图形在饼图数据中的下标
|
||||
const highlightPie = (option, myChart) => {
|
||||
// 遍历饼图数据,取消所有图形的高亮效果
|
||||
for (var idx in option) {
|
||||
myChart.dispatchAction({
|
||||
type: 'downplay',
|
||||
seriesIndex: 0,
|
||||
dataIndex: idx
|
||||
})
|
||||
}
|
||||
// 高亮当前图形
|
||||
myChart.dispatchAction({
|
||||
type: 'highlight',
|
||||
seriesIndex: 0,
|
||||
dataIndex: currentIndex.value
|
||||
})
|
||||
|
||||
}
|
||||
// 饼图自动轮播
|
||||
const handleChartLoop = (option, myChart) => {
|
||||
if (!myChart) {
|
||||
return
|
||||
}
|
||||
highlightPie(option, myChart)
|
||||
// 用户鼠标悬浮到某一图形时,高亮鼠标悬浮的图形
|
||||
myChart.on('mouseover', (params) => {
|
||||
currentIndex.value = params.dataIndex
|
||||
highlightPie(option, myChart)
|
||||
})
|
||||
}
|
||||
let optionSeriesData = ref<OptionSeriesData[]>([])
|
||||
const option = reactive({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
left: 'right',
|
||||
orient: 'vertical',
|
||||
align: 'left',
|
||||
top: 'center',
|
||||
icon: 'circle',
|
||||
itemGap: 25,
|
||||
// 图例文本格式化
|
||||
formatter: function (name) {
|
||||
// 在这里可以获取到对应数据项的值
|
||||
var data = optionSeriesData.value
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (data[i].name === name) {
|
||||
return name + ' ' + data[i].value
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
},
|
||||
color: ['#50b5ff', '#ffc542', '#ff7474', '#50b5ff', '#8167f5'],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
formatter: '{d}%\n\n{b}',
|
||||
fontSize: 20
|
||||
},
|
||||
legendHoverLink: false,
|
||||
center: ['30%', '45%'],
|
||||
data: [],
|
||||
labelLine: {
|
||||
show: false // 关闭指示线
|
||||
},
|
||||
radius: ['48%', '70%'],
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
//漏斗
|
||||
const ldOption = reactive({
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
containLabel: true
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '',
|
||||
type: 'funnel',
|
||||
left: '10%',
|
||||
top: 20,
|
||||
bottom: 60,
|
||||
width: '75%',
|
||||
min: 0,
|
||||
max: 50000,
|
||||
minSize: '30%',
|
||||
maxSize: '70%',
|
||||
sort: 'descending',
|
||||
gap: 0,
|
||||
color: ['#5b8ff9', '#5ad8a6', '#5d7092', '#f6bd16'],
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
formatter: '{b} : {c}'
|
||||
},
|
||||
itemStyle: {
|
||||
borderWidth: 0
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
//日交易额
|
||||
let dailyTurnoverXAxis = ref<string[]>([])
|
||||
let dailyTurnoverSeries = ref<number[]>([])
|
||||
const zxtOption = reactive({
|
||||
grid: {
|
||||
top: '10%', // 调整顶部的空白间距,也可以使用像素值,例如 '50px'
|
||||
bottom: '20%'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dailyTurnoverXAxis.value,
|
||||
axisTick: {
|
||||
show: false // 设置X轴不显示刻度
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
offset: -7,
|
||||
max: '50000',
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
fontSize: 12 // 增大字体大小
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: dailyTurnoverSeries.value,
|
||||
type: 'line',
|
||||
symbol: 'none',
|
||||
smooth: false, // 是否平滑曲线显示
|
||||
lineStyle: {
|
||||
color: '#51d351', // 折线颜色
|
||||
width: 2 // 折线宽度
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 周订单量
|
||||
let weeklyOrdersXAxis = ref<string[]>([])
|
||||
let weeklyOrdersSeries = ref<number[]>([])
|
||||
const zddlOption = reactive({
|
||||
grid: {
|
||||
top: '10%', // 调整顶部的空白间距,也可以使用像素值,例如 '50px'
|
||||
bottom: '20%'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: weeklyOrdersXAxis.value,
|
||||
axisTick: {
|
||||
show: false // 设置X轴不显示刻度
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: weeklyOrdersSeries.value,
|
||||
type: 'bar',
|
||||
barWidth: '35%',
|
||||
itemStyle: {
|
||||
// 设置柱形图的颜色
|
||||
color: '#409eff',
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
//开拓商家
|
||||
let expansionNumberDataset = ref<Ktsj[]>([['product', '目标开拓数', '实际开拓数']])
|
||||
const ktsjOption = reactive({
|
||||
theme: 'dark',
|
||||
grid: {
|
||||
top: '15%', // 调整顶部的空白间距,也可以使用像素值,例如 '50px'
|
||||
bottom: '20%'
|
||||
},
|
||||
legend: {
|
||||
icon: 'circle' // 设置图例标记为圆形
|
||||
},
|
||||
tooltip: {},
|
||||
dataset: {
|
||||
source: expansionNumberDataset.value
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisTick: {
|
||||
show: false // 设置X轴不显示刻度
|
||||
}
|
||||
},
|
||||
yAxis: {},
|
||||
// Declare several bar series, each will be mapped
|
||||
// to a column of dataset.source by default.
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: '20%',
|
||||
itemStyle: {
|
||||
// 设置柱形图的颜色
|
||||
color: '#52c1f5',
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: '20%',
|
||||
itemStyle: {
|
||||
// 设置柱形图的颜色
|
||||
color: '#ff7a8c',
|
||||
borderRadius: [5, 5, 0, 0]
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
//成交金额趋势
|
||||
const tabPosition = ref('最近1周')
|
||||
const cjjeTabList = ref([
|
||||
{
|
||||
label: '最近1周',
|
||||
type: 1
|
||||
},
|
||||
{
|
||||
label: '最近30天',
|
||||
type: 2
|
||||
},
|
||||
{
|
||||
label: '最近半年',
|
||||
type: 3
|
||||
},
|
||||
{
|
||||
label: '最近1年',
|
||||
type: 4
|
||||
}
|
||||
])
|
||||
let transactionAmountXAxis = ref<string[]>([])
|
||||
let transactionAmountSeries1 = ref<string[]>([])
|
||||
let transactionAmountSeries2 = ref<string[]>([])
|
||||
const cjjeOption = ref({
|
||||
grid: {
|
||||
left: '4%',
|
||||
top: '5%', // 调整顶部的空白间距,也可以使用像素值,例如 '50px'
|
||||
bottom: '20%'
|
||||
},
|
||||
legend: {
|
||||
top: 'bottom',
|
||||
data: ['线上成交', '线下成交'],
|
||||
icon: 'rectangle'
|
||||
},
|
||||
xAxis: {
|
||||
data: transactionAmountXAxis.value
|
||||
},
|
||||
yAxis: {},
|
||||
series: [
|
||||
{
|
||||
name: '线上成交',
|
||||
data: transactionAmountSeries1.value,
|
||||
type: 'line',
|
||||
symbol: 'none',
|
||||
smooth: false,
|
||||
areaStyle: {
|
||||
color: '#b2e0ff',
|
||||
opacity: 0.8
|
||||
}
|
||||
// symbol: 'circle',
|
||||
},
|
||||
{
|
||||
name: '线下成交',
|
||||
data: transactionAmountSeries2.value,
|
||||
type: 'line',
|
||||
symbol: 'none',
|
||||
smooth: false,
|
||||
areaStyle: {
|
||||
color: '#ffd7dc',
|
||||
opacity: 0.8
|
||||
}
|
||||
// symbol: 'circle',
|
||||
}
|
||||
]
|
||||
})
|
||||
const clickCjje = async (type) => {
|
||||
let res = await getTableList('example_trader_transaction_amount', { type: type })
|
||||
transactionAmountXAxis.value = []
|
||||
transactionAmountSeries1.value = []
|
||||
transactionAmountSeries2.value = []
|
||||
res.records.forEach((ele) => {
|
||||
transactionAmountXAxis.value.push(ele.sj)
|
||||
transactionAmountSeries1.value.push(ele.xscj)
|
||||
transactionAmountSeries2.value.push(ele.xxcj)
|
||||
})
|
||||
cjjeOption.value.xAxis.data = transactionAmountXAxis.value
|
||||
cjjeOption.value.series[0].data = transactionAmountSeries1.value
|
||||
cjjeOption.value.series[1].data = transactionAmountSeries2.value
|
||||
nextTick(() => {
|
||||
const cjjeChart = echarts.init(document.getElementById('cjjeMain'))
|
||||
cjjeChart.setOption(cjjeOption.value)
|
||||
})
|
||||
}
|
||||
|
||||
// 通用函数,用于初始化 ECharts 图表
|
||||
const initEcharts = (domElementId, option) => {
|
||||
const domElement = document.getElementById(domElementId)
|
||||
if (domElement) {
|
||||
const chart = echarts.init(domElement)
|
||||
domElement.removeAttribute('_echarts_instance_')
|
||||
chart.setOption(option)
|
||||
if (domElementId == 'main') {
|
||||
handleChartLoop(option.series[0].data, chart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const echartFun = () => {
|
||||
initEcharts('ldMain', ldOption)
|
||||
initEcharts('zxtMain', zxtOption)
|
||||
initEcharts('zddlMain', zddlOption)
|
||||
initEcharts('ktsjMain', ktsjOption)
|
||||
initEcharts('main', option)
|
||||
initEcharts('cjjeMain', cjjeOption.value)
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
let oneres = await batchGetTableList(
|
||||
'example_trader_count_data,example_trader_expansion,example_trader_merchant_distribution,example_trader_transaction_funneldiagram'
|
||||
)
|
||||
|
||||
topCardList.value = topCardList.value.map((item, index) => {
|
||||
const cardData = oneres.example_trader_count_data.records[0][item.dictionary]
|
||||
item = { ...item, ...cardData, border: 'border' + index }
|
||||
return item
|
||||
})
|
||||
jdtData.value = jdtData.value.map((item) => {
|
||||
const cardData = oneres.example_trader_expansion.records[0][item.dictionary]
|
||||
item = { ...item, ...cardData, jd: (cardData.leftNum / cardData.rightNum) * 100 }
|
||||
return item
|
||||
})
|
||||
option.series[0].data = oneres.example_trader_merchant_distribution.records
|
||||
optionSeriesData.value = oneres.example_trader_merchant_distribution.records
|
||||
ldOption.series[0].data = oneres.example_trader_transaction_funneldiagram?.records
|
||||
|
||||
let endres = await batchGetTableList(
|
||||
'example_trader_daily_turnover,example_trader_weekly_orders,example_trader_expansion_number,example_trader_transaction_amount'
|
||||
)
|
||||
|
||||
endres.example_trader_daily_turnover?.records.forEach((ele) => {
|
||||
dailyTurnoverXAxis.value.push(ele.sj)
|
||||
dailyTurnoverSeries.value.push(ele.data)
|
||||
})
|
||||
zxtOption.xAxis.data = dailyTurnoverXAxis.value
|
||||
zxtOption.series[0].data = dailyTurnoverSeries.value
|
||||
endres.example_trader_weekly_orders.records.forEach((ele) => {
|
||||
weeklyOrdersXAxis.value.push(ele.sj)
|
||||
weeklyOrdersSeries.value.push(ele.data)
|
||||
})
|
||||
zddlOption.xAxis.data = weeklyOrdersXAxis.value
|
||||
zddlOption.series[0].data = weeklyOrdersSeries.value
|
||||
expansionNumberDataset.value.push(
|
||||
...endres.example_trader_expansion_number.records.map((item) => [
|
||||
item.sj,
|
||||
item.mbkts,
|
||||
item.sjkts
|
||||
])
|
||||
)
|
||||
endres.example_trader_transaction_amount.records.forEach((ele) => {
|
||||
transactionAmountXAxis.value.push(ele.sj)
|
||||
transactionAmountSeries1.value.push(ele.xscj)
|
||||
transactionAmountSeries2.value.push(ele.xxcj)
|
||||
})
|
||||
cjjeOption.value.xAxis.data = transactionAmountXAxis.value
|
||||
cjjeOption.value.series[0].data = transactionAmountSeries1.value
|
||||
cjjeOption.value.series[1].data = transactionAmountSeries2.value
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
echartFun()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.real-time-data {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #999;
|
||||
align-items: flex-end;
|
||||
|
||||
.title {
|
||||
// color: #666666;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.top-box {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-card {
|
||||
flex-basis: calc(16.6% - 12px);
|
||||
flex-shrink: 1;
|
||||
min-width: 168px;
|
||||
padding: 15px;
|
||||
// border-radius: 10px;
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
box-sizing: border-box;
|
||||
|
||||
.card-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 32px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.tendency {
|
||||
width: 24px;
|
||||
height: 17px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.text-ts {
|
||||
margin: 0 3px;
|
||||
font-family: Montserrat, sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-left: 1px;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.text-value {
|
||||
font-family: 'Montserrat Bold', Montserrat, sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
line-height: 32px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.card-box.top {
|
||||
.text {
|
||||
color: rgb(7 183 138);
|
||||
}
|
||||
|
||||
.ascent {
|
||||
background-image: url('@/assets/svgs/top.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.card-box.bottom {
|
||||
.text {
|
||||
color: rgb(250 80 135);
|
||||
}
|
||||
|
||||
.descent {
|
||||
background-image: url('@/assets/svgs/bottom.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.text-dw {
|
||||
margin-left: 8px;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
padding: 10px 30px 0;
|
||||
margin-top: 20px;
|
||||
background: #fff;
|
||||
justify-content: space-between;
|
||||
|
||||
p {
|
||||
height: 60px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.content-left {
|
||||
width: 30%;
|
||||
|
||||
.jdt {
|
||||
margin-top: 20px;
|
||||
|
||||
.jdt-top {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
|
||||
.jdt-top-text {
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.num {
|
||||
font-family: 'Montserrat Bold', Montserrat, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.acitive {
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 5px;
|
||||
// background: red;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.jdt-bottom {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-center {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.content-right {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
.eCharts {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
justify-content: space-between;
|
||||
|
||||
.eCharts-title {
|
||||
display: flex;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
justify-content: space-between;
|
||||
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.eCharts-left {
|
||||
width: 27.5%;
|
||||
padding: 10px 30px 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.eCharts-center {
|
||||
width: 27.5%;
|
||||
padding: 10px 30px 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.eCharts-right {
|
||||
width: 27.5%;
|
||||
padding: 10px 30px 0;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.eCharts-cjje {
|
||||
padding: 10px 30px 30px;
|
||||
margin-top: 20px;
|
||||
background: #fff;
|
||||
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cjje-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.cjje-top-left {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
color: #686a6d;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.border0 {
|
||||
border-left: 5px solid #fb6260;
|
||||
}
|
||||
|
||||
.border1 {
|
||||
border-left: 5px solid #09f;
|
||||
}
|
||||
|
||||
.border2 {
|
||||
border-left: 5px solid #8167f5;
|
||||
}
|
||||
|
||||
.border3 {
|
||||
border-left: 5px solid #51d351;
|
||||
}
|
||||
|
||||
.border4 {
|
||||
border-left: 5px solid #ff7a8c;
|
||||
}
|
||||
|
||||
.border5 {
|
||||
border-left: 5px solid #ffa94c;
|
||||
}
|
||||
|
||||
.text-zeng {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.text-jian {
|
||||
color: #19be6b;
|
||||
}
|
||||
|
||||
.demo-progress .el-progress--line {
|
||||
max-width: 600px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.bj0 {
|
||||
background: #409eff;
|
||||
}
|
||||
|
||||
.bj1 {
|
||||
background: #f56c6c;
|
||||
}
|
||||
|
||||
.bj2 {
|
||||
background: #e6a23c;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.icons {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.font {
|
||||
font-family: 'Montserrat Bold', Montserrat, sans-serif !important;
|
||||
}
|
||||
|
||||
.fontFamily {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important;
|
||||
}
|
||||
|
||||
::v-deep .el-progress-bar__innerText {
|
||||
margin-top: -8px !important;
|
||||
line-height: 12px !important;
|
||||
}
|
||||
|
||||
::v-deep .style {
|
||||
top: -50px !important;
|
||||
}
|
||||
</style>
|
||||
799
src/views/Home/Index7.vue
Normal file
799
src/views/Home/Index7.vue
Normal file
@@ -0,0 +1,799 @@
|
||||
<template>
|
||||
<div class="w-100%">
|
||||
<div class="box">
|
||||
<div class="box-left">
|
||||
<div class="top .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<p class="title .dark:c-#fff!">我的待办</p>
|
||||
<div class="top-content">
|
||||
<div class="top-content-box" v-for="(item, index) in data" :key="index">
|
||||
<img :src="item.img" alt="" />
|
||||
<div style="margin-left: 15px">
|
||||
<p class="text .dark:c-#fff!" style="font-size: 28px">{{ item.num }}</p>
|
||||
<p class="text font .dark:c-#ccc!">{{ item.title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<p class="title .dark:c-#fff!">常用功能</p>
|
||||
<div class="center-box">
|
||||
<div class="content-box" v-for="(item, index) in cygnData" :key="index">
|
||||
<div class="img" :class="'bj' + index">
|
||||
<img :src="item.src" alt="" />
|
||||
</div>
|
||||
<div class="font .dark:c-#fff!" style="margin-top: 15px">{{ item.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div class="bottom-top">
|
||||
<p class="bottom-top-title .dark:c-#fff!">业务处理</p>
|
||||
<el-menu
|
||||
:default-active="cwclType"
|
||||
:ellipsis="false"
|
||||
mode="horizontal"
|
||||
@select="handleSelect"
|
||||
style="padding: 0 !important; margin: 0 !important"
|
||||
>
|
||||
<el-menu-item v-for="item in cwclTypeList" :key="item.type" :index="item.type">{{
|
||||
item.name
|
||||
}}</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
<el-table
|
||||
class="custom-row-gap"
|
||||
:data="cwclTableData"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{
|
||||
height: '50px',
|
||||
fontFamily: `'微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important`,
|
||||
fontWeight: ' 700 !important',
|
||||
color: '#666666 !important'
|
||||
}"
|
||||
:cell-style="{
|
||||
height: '44.2px',
|
||||
fontFamily: `'微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important`,
|
||||
fontWeight: ' 400 !important',
|
||||
color: '#666666 !important'
|
||||
}"
|
||||
>
|
||||
<el-table-column prop="tjsj" label="提交时间" width="170" />
|
||||
<el-table-column prop="bxlx" label="报销类型" width="140" />
|
||||
<el-table-column prop="spdh" label="审批单号" width="140" />
|
||||
<el-table-column prop="bxje" label="报销金额" width="140" />
|
||||
<el-table-column prop="spdx" label="审批对象" width="140" />
|
||||
<el-table-column fixed="right" label="操作" min-width="120" align="center">
|
||||
<template #default>
|
||||
<el-button link type="primary" size="small" @click="handleClick">
|
||||
<Icon
|
||||
icon="icon-park-solid:right-c"
|
||||
width="12"
|
||||
height="12"
|
||||
style="color: #2391ff"
|
||||
/>
|
||||
<span style="margin-left: 5px; font-size: 14px">查看详情</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-right">
|
||||
<div class="box-right-top .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div class="title .dark:c-#fff!">预警信息</div>
|
||||
<div class="list" v-for="(item, index) in yjxxdata" :key="index">
|
||||
<span class="list-left .dark:c-#ccc!">{{ item.name }}</span>
|
||||
<span class="list-right" :class="index % 2 == 0 ? 'text-color' : ''">{{
|
||||
item.num
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-right-center .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<div class="title .dark:c-#fff!"> 年度经营目标</div>
|
||||
<div class="title" style="font-size: 24px; color: #279df5">
|
||||
<span class="fontFamily .dark:c-#fff!">{{ annualIndicators1?.ndjymb }}</span>
|
||||
<span style="font-size: 14px; color: #666" class=".dark:c-#ccc!"> 万 </span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ndmb">
|
||||
<el-progress
|
||||
type="circle"
|
||||
:percentage="annualIndicators1?.wcl"
|
||||
:stroke-width="16"
|
||||
:width="166"
|
||||
>
|
||||
<template #default>
|
||||
<span class="percentage-label .dark:c-#ccc!" style="color: #20a0ff"> 完成率 </span>
|
||||
<span class="percentage-value" style="color: #20a0ff">
|
||||
{{ annualIndicators1?.wcl }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-progress>
|
||||
</div>
|
||||
<div class="text .dark:c-#ccc!">
|
||||
已完成目标:
|
||||
<span class="fontFamily" style="font-size: 24px; font-weight: 700; color: #fbaf4f">
|
||||
{{ annualIndicators1?.ywcmb }}
|
||||
</span>
|
||||
万
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-right-center .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<div class="title .dark:c-#fff!">年度回款目标</div>
|
||||
<div class="title" style="font-size: 24px; color: #279df5">
|
||||
<span class="fontFamily .dark:c-#fff!">{{ annualIndicators2?.ndhkmb }}</span>
|
||||
<span style="font-size: 14px; color: #666" class=".dark:c-#ccc!"> 万</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ndmb">
|
||||
<el-progress
|
||||
type="circle"
|
||||
:percentage="annualIndicators2?.wcl"
|
||||
:stroke-width="16"
|
||||
status="success"
|
||||
:width="166"
|
||||
>
|
||||
<template #default>
|
||||
<span class="percentage-label .dark:c-#ccc!">完成率</span>
|
||||
<span class="percentage-value">{{ annualIndicators2?.wcl }}%</span>
|
||||
</template>
|
||||
</el-progress>
|
||||
</div>
|
||||
<div class="text .dark:c-#ccc!">
|
||||
已完成目标
|
||||
<span class="fontFamily" style="font-size: 24px; font-weight: 700; color: #fbaf4f">
|
||||
{{ annualIndicators2?.ywcmb }}
|
||||
</span>
|
||||
万
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-100% eCharts">
|
||||
<div class="eCharts-left .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div class="eCharts-left-top">
|
||||
<div class="title .dark:c-#fff!">账户余额</div>
|
||||
<div class="font .dark:c-#ccc!">
|
||||
<span>合计:</span>
|
||||
<span
|
||||
class="fontFamily .dark:c-#fff!"
|
||||
style="font-size: 20px; font-weight: 700; color: #2b9ef7"
|
||||
>{{ zhyeNum }}</span
|
||||
>
|
||||
<span style="margin-left: 5px">万</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
style="display: flex; align-items: center; line-height: 50px"
|
||||
v-for="(item, index) in zhyedata"
|
||||
:key="index"
|
||||
>
|
||||
<div class="font-title .dark:c-#ccc!" style="width: 17%">{{ item.name }}</div>
|
||||
<div style="width: 79%">
|
||||
<el-progress :percentage="item.jdt" :stroke-width="10">
|
||||
<span
|
||||
style="
|
||||
margin-left: 30px;
|
||||
font-family: ArialMT, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
"
|
||||
>{{ item.num }}</span
|
||||
>
|
||||
</el-progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eCharts-right .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div class="eCharts-right-top">
|
||||
<div class="title .dark:c-#fff!">收支预测</div>
|
||||
</div>
|
||||
<div id="szycMain" style="width: 600px; height: 300px"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-100% eCharts">
|
||||
<div class="eCharts-left .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div class="eCharts-left-top">
|
||||
<div class="title .dark:c-#fff!">费用结构</div>
|
||||
</div>
|
||||
<div id="fyjgMain" style="width: 600px; height: 300px"></div>
|
||||
</div>
|
||||
<div class="eCharts-right .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<div class="eCharts-right-top">
|
||||
<div class="title .dark:c-#fff!">收入结构</div>
|
||||
</div>
|
||||
<div id="srjgMain" style="width: 600px; height: 300px"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as echarts from 'echarts'
|
||||
import { getTableList, batchGetTableList } from '@/api/design/report'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
interface Data {
|
||||
dictionary: string
|
||||
img: string
|
||||
title: string
|
||||
num?: string
|
||||
}
|
||||
interface CwclTableData {
|
||||
tjsj: string
|
||||
bxlx: string
|
||||
spdh: string
|
||||
bxje: string
|
||||
spdx: string
|
||||
}
|
||||
|
||||
interface Yjxxdata {
|
||||
dictionary: string
|
||||
name: string
|
||||
num?: string
|
||||
}
|
||||
interface CircularProgress {
|
||||
ndjymb: number
|
||||
wcl: number
|
||||
ywcmb: number
|
||||
ndhkmb: number
|
||||
}
|
||||
interface Zhyedata {
|
||||
name: string
|
||||
num: number
|
||||
jdt: number
|
||||
}
|
||||
type Szyc = [[string, string | number, string | number]]
|
||||
|
||||
//我的待办
|
||||
let data = ref<Data[]>([
|
||||
{ dictionary: 'dclgd', img: '/img/img1.svg', title: '待处理工单' },
|
||||
{ dictionary: 'dbxmx', img: '/img/img2.svg', title: '待报销明细' },
|
||||
{ dictionary: 'dtjhb', img: '/img/img3.svg', title: '待提交汇报' },
|
||||
{ dictionary: 'dtjbb', img: '/img/img4.svg', title: '待提交报表' }
|
||||
])
|
||||
|
||||
//常用功能
|
||||
const cygnData = reactive([
|
||||
{ src: '/img/tb1.svg', title: '资金收支报表' },
|
||||
{ src: '/img/tb2.svg', title: '资产负债报表' },
|
||||
{ src: '/img/tb3.svg', title: '企业利润报表' },
|
||||
{ src: '/img/tb4.svg', title: '现金流量报表' },
|
||||
{ src: '/img/tb5.svg', title: '项目报告列表' },
|
||||
{ src: '/img/tb6.svg', title: '责任中心报告' }
|
||||
])
|
||||
|
||||
//预警信息
|
||||
let yjxxdata = ref<Yjxxdata[]>([
|
||||
{
|
||||
dictionary: 'dqysk',
|
||||
name: '到期应收款'
|
||||
},
|
||||
{
|
||||
dictionary: 'yqysk',
|
||||
name: '逾期应收款'
|
||||
},
|
||||
{
|
||||
dictionary: 'dqyfk',
|
||||
name: '到期应付款'
|
||||
},
|
||||
{
|
||||
dictionary: 'yqyfk',
|
||||
name: '逾期应付款'
|
||||
}
|
||||
])
|
||||
|
||||
// 年度经营目标
|
||||
const annualIndicators1 = ref<CircularProgress>()
|
||||
|
||||
// 年度回款目标
|
||||
const annualIndicators2 = ref<CircularProgress>()
|
||||
|
||||
//账户余额
|
||||
const zhyeNum = ref('')
|
||||
let zhyedata = ref<Zhyedata[]>()
|
||||
|
||||
//收支预测
|
||||
const szycDataset = ref<Szyc>([['product', '预计收入', '预计支出']])
|
||||
const szycOption = reactive({
|
||||
legend: {
|
||||
// data: ['系列1', '系列2'],
|
||||
orient: 'horizontal',
|
||||
x: 'center', // 可以是 'center'、'left'、'right' 等
|
||||
y: 'bottom' // 可以是 'top'、'center'、'bottom' 等,或者是具体的像素值
|
||||
},
|
||||
tooltip: {},
|
||||
dataset: {
|
||||
source: szycDataset.value
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisTick: {
|
||||
show: false // 设置X轴不显示刻度
|
||||
}
|
||||
},
|
||||
yAxis: {},
|
||||
// Declare several bar series, each will be mapped
|
||||
// to a column of dataset.source by default.
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
itemStyle: {
|
||||
// 设置柱形图的颜色
|
||||
color: '#2c9ef7'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
itemStyle: {
|
||||
// 设置柱形图的颜色
|
||||
color: '#0dd78d'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
//费用结构
|
||||
const fyjgOption = reactive({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
left: 'center',
|
||||
top: 'bottom',
|
||||
icon: 'circle'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '',
|
||||
type: 'pie',
|
||||
radius: '58%',
|
||||
center: ['50%', '40%'],
|
||||
color: ['#2c9ef7', '#aa89fe', '#fdad4e', '#0dd78d'],
|
||||
data: [],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
formatter: '{b}\n\n¥{c}',
|
||||
textStyle: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
//收入结构
|
||||
const srjgOption = reactive({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
left: 'center',
|
||||
top: 'bottom',
|
||||
icon: 'circle'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '40%'],
|
||||
color: ['#2c9ef7', '#5cb9ff', '#abdbff', '#d0ebff'],
|
||||
data: [],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
formatter: '{b}\n \n ¥{c}',
|
||||
textStyle: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 业务处理
|
||||
const cwclType = ref('1')
|
||||
const cwclTableData = ref<CwclTableData[]>()
|
||||
const cwclTypeList = ref([
|
||||
{ name: '待我审批', type: '1' },
|
||||
{ name: '超期待审', type: '2' },
|
||||
{ name: '我提交的', type: '3' },
|
||||
{ name: '被退回的', type: '4' }
|
||||
])
|
||||
|
||||
const handleSelect = async (key: string, keyPath: string[]) => {
|
||||
let res = await getTableList('example_systemmonitor_business_processing', { type: key })
|
||||
cwclTableData.value = res.records
|
||||
}
|
||||
const handleClick = () => {
|
||||
ElMessage({
|
||||
message: '查看详情',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
// 通用函数,用于初始化 ECharts 图表
|
||||
const initEcharts = (domElementId, option) => {
|
||||
const domElement = document.getElementById(domElementId)
|
||||
if (domElement) {
|
||||
const chart = echarts.init(domElement)
|
||||
domElement.removeAttribute('_echarts_instance_')
|
||||
chart.setOption(option)
|
||||
}
|
||||
}
|
||||
|
||||
const echartFun = () => {
|
||||
initEcharts('szycMain', szycOption)
|
||||
initEcharts('fyjgMain', fyjgOption)
|
||||
initEcharts('srjgMain', srjgOption)
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
let oneres = await batchGetTableList(
|
||||
'example_signagetwo_pending,example_systemmonitor_business_processing,example_systemmonitor_warning_messages,example_systemmonitor_business_target'
|
||||
)
|
||||
let signagetwoPending = oneres.example_signagetwo_pending.records[0]
|
||||
let businessProcessing = oneres.example_systemmonitor_business_processing.records
|
||||
let warningMessages = oneres.example_systemmonitor_warning_messages.records[0]
|
||||
let businessTarget = oneres.example_systemmonitor_business_target.records[0]
|
||||
data.value = data.value.map((item) => {
|
||||
return (item = {
|
||||
...item,
|
||||
num: signagetwoPending[item.dictionary]
|
||||
})
|
||||
})
|
||||
cwclTableData.value = businessProcessing
|
||||
yjxxdata.value = yjxxdata.value.map((item) => {
|
||||
return (item = {
|
||||
...item,
|
||||
num: warningMessages[item.dictionary]
|
||||
})
|
||||
})
|
||||
annualIndicators1.value = businessTarget
|
||||
|
||||
let endres = await batchGetTableList(
|
||||
'example_systemmonitor_payback_target,example_systemmonitor_account_balance,example_systemmonitor_Income_expenditure_forecasts,example_systemmonitor_fee_structure,example_systemmonitor_Income_structure'
|
||||
)
|
||||
|
||||
let paybackTarget = endres.example_systemmonitor_payback_target.records[0]
|
||||
let accountBalance = endres.example_systemmonitor_account_balance.records
|
||||
let expenditureForecasts = endres.example_systemmonitor_Income_expenditure_forecasts.records
|
||||
let feeStructure = endres.example_systemmonitor_fee_structure.records
|
||||
let incomestructure = endres.example_systemmonitor_Income_structure.records
|
||||
annualIndicators2.value = paybackTarget
|
||||
zhyedata.value = accountBalance
|
||||
zhyeNum.value = accountBalance.reduce((a, b) => {
|
||||
return a + b.num
|
||||
}, 0)
|
||||
expenditureForecasts.forEach((ele) => {
|
||||
szycDataset.value.push([ele.sj, ele.yjsr, ele.yjzc])
|
||||
})
|
||||
szycOption.dataset.source = szycDataset.value
|
||||
fyjgOption.series[0].data = feeStructure
|
||||
srjgOption.series[0].data = incomestructure
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
echartFun()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.box-left {
|
||||
width: 72.3%;
|
||||
// height: 300px;
|
||||
// background: red;
|
||||
|
||||
.title {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 60px;
|
||||
color: #333;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.top {
|
||||
padding: 0 20px 20px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
|
||||
.text {
|
||||
font-family: 'Montserrat Bold', 'Montserrat Regular', Montserrat, sans-serif;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.top-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 25px;
|
||||
|
||||
.top-content-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
padding: 0 20px 10px;
|
||||
margin-top: 20px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
|
||||
.center-box {
|
||||
display: flex;
|
||||
padding-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
justify-content: space-evenly;
|
||||
|
||||
.content-box {
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
// background: red;
|
||||
|
||||
.img {
|
||||
display: flex;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: 0 auto;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
padding: 0 20px;
|
||||
padding-bottom: 35px;
|
||||
margin-top: 20px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
|
||||
.bottom-top {
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
justify-content: space-between;
|
||||
|
||||
.bottom-top-title {
|
||||
height: 40px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 52px;
|
||||
color: #333;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.box-right {
|
||||
width: 26.3%;
|
||||
// height: 300px;
|
||||
// background: blue;
|
||||
|
||||
.title {
|
||||
// margin-bottom: 20px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 60px;
|
||||
color: #333;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.box-right-top {
|
||||
padding: 0 20px 20px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
// margin-bottom: 15px;
|
||||
line-height: 40px;
|
||||
|
||||
.list-left {
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.list-right {
|
||||
font-family: 'Montserrat Bold', 'Montserrat Regular', Montserrat, sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
color: #0fd1b6;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.box-right-center {
|
||||
padding: 0 20px 20px;
|
||||
margin-top: 20px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
|
||||
.ndmb {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-top: 20px;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 40px;
|
||||
color: #797979;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.eCharts {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 60px;
|
||||
color: #333;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.eCharts-left {
|
||||
width: 46.1%;
|
||||
padding: 0 20px 20px;
|
||||
line-height: 60px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
|
||||
.eCharts-left-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.eCharts-right-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.eCharts-right {
|
||||
width: 46.1%;
|
||||
padding: 0 20px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.percentage-value {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
font-family: Arial-BoldMT, 'Arial Bold', Arial, sans-serif;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #13ce66;
|
||||
}
|
||||
|
||||
.percentage-label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
font-family: ArialMT, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
.el-menu--horizontal.el-menu {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.bj0 {
|
||||
background: #2c9ef7;
|
||||
}
|
||||
|
||||
.bj1 {
|
||||
background: #b591f5;
|
||||
}
|
||||
|
||||
.bj2 {
|
||||
background: #11d88f;
|
||||
}
|
||||
|
||||
.bj3 {
|
||||
background: #ffad52;
|
||||
}
|
||||
|
||||
.bj4 {
|
||||
background: #10d1b7;
|
||||
}
|
||||
|
||||
.bj5 {
|
||||
background: #f67263;
|
||||
}
|
||||
|
||||
.text-color {
|
||||
color: #f6716e !important;
|
||||
}
|
||||
|
||||
.font {
|
||||
font-family: '微软雅黑', sans-serif !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 400 !important;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.fontFamily {
|
||||
font-family: 'Montserrat Bold', 'Montserrat Regular', Montserrat, sans-serif !important;
|
||||
}
|
||||
|
||||
.font-title {
|
||||
font-family: MicrosoftYaHei, '微软雅黑 Regular', '微软雅黑', sans-serif !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
::v-deep .el-table--default .el-table__cell {
|
||||
padding: 0 !important; /* 根据需要调整这个值来增加或减少行间距 */
|
||||
}
|
||||
</style>
|
||||
432
src/views/Home/Index8.vue
Normal file
432
src/views/Home/Index8.vue
Normal file
@@ -0,0 +1,432 @@
|
||||
<template>
|
||||
<div class="w-100%">
|
||||
<div class="w-100% flex gap-y-20px home-top">
|
||||
<div class="el-card top-card flex items-center" v-for="(item, index) in tabList" :key="index">
|
||||
<div class="left">
|
||||
<span class="text-value">
|
||||
<avue-count-up :end="item.countUp"></avue-count-up>
|
||||
</span>
|
||||
<div class="text mt-10px">
|
||||
<span class="mr-5px">{{ item.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-image :src="item.imgUrl" fit="contain"></el-image>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-100% flex gap-x-20px mt-30px ml-10px mr-10px">
|
||||
<div class="w-35%">
|
||||
<el-calendar v-model="value">
|
||||
<template #header>
|
||||
<span>{{ calendarDate }}</span>
|
||||
</template>
|
||||
</el-calendar>
|
||||
</div>
|
||||
<div class="echart-util bold bg-white .dark:bg-#1D1E1F">
|
||||
<Echart :options="visitor_option" width="100%" height="314px" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-100% flex gap-x-20px mt-30px ml-10px mr-10px">
|
||||
<div class="echart-util bold bg-white .dark:bg-#1D1E1F">
|
||||
<Echart :options="salesVolume_option" width="100%" height="314px" />
|
||||
</div>
|
||||
<div class="echart-util reverse bg-white .dark:bg-#1D1E1F">
|
||||
<Echart :options="cyclic_annular_option" width="100%" height="314px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { batchGetTableList } from '@/api/design/report'
|
||||
|
||||
defineOptions({ name: 'Home8' })
|
||||
|
||||
interface CyclicAnnular {
|
||||
name: string
|
||||
dictionary: string
|
||||
value?: number
|
||||
}
|
||||
|
||||
const tabList = ref([
|
||||
{ countUp: 0, title: '今日访客数(人)', imgUrl: '/img/sjhy1.png', dictionary: 'today_visitors' },
|
||||
{
|
||||
countUp: 0,
|
||||
title: '今日会员访问数(人)',
|
||||
imgUrl: '/img/sjhy2.png',
|
||||
dictionary: 'today_member_visitors'
|
||||
},
|
||||
{
|
||||
countUp: 0,
|
||||
title: '今日会员付费数(人)',
|
||||
imgUrl: '/img/sjhy3.png',
|
||||
dictionary: 'today_paid_member'
|
||||
},
|
||||
{ countUp: 0, title: '新增会员数(人)', imgUrl: '/img/sjhy4.png', dictionary: 'today_new_member' }
|
||||
])
|
||||
|
||||
const value = ref(new Date())
|
||||
const calendarDate = formatDate(new Date(), 'YYYY-MM-DD')
|
||||
|
||||
// 实时访客量
|
||||
const visitorXAxis = ref<string[]>([])
|
||||
const visitorSeries = ref<number[]>([])
|
||||
const visitor_option = ref({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
title: {
|
||||
text: '实时访客量',
|
||||
textStyle: {
|
||||
fontSize: 20,
|
||||
color: 'black'
|
||||
},
|
||||
left: 25
|
||||
},
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: '#EDE8FE' // 0% 处的颜色
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#F8F7FC' // 100% 处的颜色
|
||||
}
|
||||
],
|
||||
global: false // 缺省为 false
|
||||
},
|
||||
grid: {
|
||||
left: '4%',
|
||||
right: '4%',
|
||||
bottom: 20,
|
||||
top: '16%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
data: visitorXAxis.value,
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: '#EBEBEB'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
color: 'black',
|
||||
fontSize: 12,
|
||||
padding: [10, 0, 0, 0],
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
color: '#6071A9',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#EBEBEB',
|
||||
type: 'solid'
|
||||
}
|
||||
},
|
||||
offset: 10
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '访客量',
|
||||
type: 'line',
|
||||
data: visitorSeries.value,
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 5,
|
||||
color: '#5D77FF'
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.7
|
||||
},
|
||||
emphasis: { disabled: true },
|
||||
symbol: 'none'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 销售额
|
||||
const salesVolumeXAxis = ref<string[]>([])
|
||||
const salesVolumeSeries = ref<number[]>([])
|
||||
const salesVolume_option = ref({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
title: {
|
||||
text: '销售额(元)',
|
||||
textStyle: {
|
||||
fontSize: 20,
|
||||
color: 'black'
|
||||
},
|
||||
left: 25
|
||||
},
|
||||
color: ['#6F7EFD'],
|
||||
grid: {
|
||||
left: '4%',
|
||||
right: '4%',
|
||||
bottom: 20,
|
||||
top: '16%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
data: salesVolumeXAxis.value,
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: '#EBEBEB'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
color: 'black',
|
||||
fontSize: 12,
|
||||
padding: [10, 0, 0, 0],
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
color: '#6071A9',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
offset: 10
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
data: salesVolumeSeries.value,
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 0
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 1
|
||||
},
|
||||
emphasis: { disabled: true },
|
||||
symbol: 'none'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 环状图
|
||||
const cyclic_annular_option_data = ref<CyclicAnnular[]>([
|
||||
{ name: '男装', dictionary: 'nz' },
|
||||
{ name: '鞋包', dictionary: 'xb' },
|
||||
{ name: '母婴', dictionary: 'my' },
|
||||
{ name: '数码', dictionary: 'sm' }
|
||||
])
|
||||
|
||||
const cyclic_annular_option = ref({
|
||||
title: {
|
||||
text: '',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
textStyle: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
color: '#5E78FD'
|
||||
},
|
||||
subtext: '',
|
||||
subtextStyle: {
|
||||
color: '#999999',
|
||||
fontWeight: '600'
|
||||
},
|
||||
itemGap: 0
|
||||
},
|
||||
color: ['#5E78FD', '#6E86FD', '#8DA0FF', '#AEBBFF'],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['35%', '55%'],
|
||||
label: {
|
||||
formatter: (parms) => {
|
||||
return [`{str|${parms.percent}% ${parms.data.name}}`].join('\n')
|
||||
},
|
||||
rich: {
|
||||
str: {
|
||||
fontWeight: 'bold',
|
||||
align: 'center'
|
||||
}
|
||||
}
|
||||
},
|
||||
data: cyclic_annular_option_data.value
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const appStore = useAppStore()
|
||||
watch(
|
||||
() => appStore.isDark,
|
||||
(val) => {
|
||||
if (val) {
|
||||
visitor_option.value.title.textStyle.color = 'white'
|
||||
salesVolume_option.value.title.textStyle.color = 'white'
|
||||
} else {
|
||||
visitor_option.value.title.textStyle.color = 'black'
|
||||
salesVolume_option.value.title.textStyle.color = 'black'
|
||||
}
|
||||
}
|
||||
)
|
||||
const init = async () => {
|
||||
let oneres = await batchGetTableList('example_member_count_data,example_member_realtime_visits')
|
||||
|
||||
let countData = oneres.example_member_count_data.records[0]
|
||||
let realtimeVisits = oneres.example_member_realtime_visits.records
|
||||
tabList.value = tabList.value.map((item) => {
|
||||
return (item = {
|
||||
...item,
|
||||
countUp: countData[item.dictionary]
|
||||
})
|
||||
})
|
||||
realtimeVisits.forEach((ele) => {
|
||||
visitorXAxis.value?.push(ele.sj)
|
||||
visitorSeries.value?.push(ele.ssfkl)
|
||||
})
|
||||
|
||||
let endres = await batchGetTableList('example_member_sale,example_member_product_percentage')
|
||||
|
||||
let sale = endres.example_member_sale.records
|
||||
let productPercentage = endres.example_member_product_percentage.records[0]
|
||||
sale.forEach((ele) => {
|
||||
salesVolumeXAxis.value?.push(ele.sj)
|
||||
salesVolumeSeries.value?.push(ele.xse)
|
||||
})
|
||||
cyclic_annular_option_data.value = cyclic_annular_option_data.value.map((item) => {
|
||||
return (item = {
|
||||
...item,
|
||||
value: productPercentage[item.dictionary]
|
||||
})
|
||||
})
|
||||
cyclic_annular_option.value.series[0].data = cyclic_annular_option_data.value
|
||||
cyclic_annular_option.value.title.text = '¥' + productPercentage.je
|
||||
cyclic_annular_option.value.title.subtext = productPercentage.sj
|
||||
}
|
||||
onMounted(async () => {
|
||||
// 判断是否为暗色模式
|
||||
const { wsCache } = useCache()
|
||||
if (wsCache.get(CACHE_KEY.IS_DARK)) {
|
||||
visitor_option.value.title.textStyle.color = 'white'
|
||||
salesVolume_option.value.title.textStyle.color = 'white'
|
||||
}
|
||||
await init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home-top {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.top-card {
|
||||
width: calc(25% - 50px);
|
||||
height: 85px;
|
||||
padding: 15px;
|
||||
margin: 0 10px;
|
||||
margin-top: 0 !important;
|
||||
border: none;
|
||||
border-radius: inherit;
|
||||
flex-shrink: 0;
|
||||
justify-content: space-around;
|
||||
box-shadow: 0 0 20px rgb(204 204 204 / 34.9%);
|
||||
|
||||
.left {
|
||||
font-weight: bold;
|
||||
|
||||
.text-value {
|
||||
margin-bottom: 45px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.echart-util {
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 20px rgb(204 204 204 / 34.9%);
|
||||
}
|
||||
|
||||
.echart-util.bold {
|
||||
width: calc(65% - 80px);
|
||||
}
|
||||
|
||||
.echart-util.reverse {
|
||||
width: calc(35% - 40px);
|
||||
}
|
||||
|
||||
::v-deep(.el-calendar) {
|
||||
box-shadow: 0 0 20px rgb(204 204 204 / 34.9%);
|
||||
|
||||
.el-calendar__header {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.el-calendar__body {
|
||||
th {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.el-calendar-table__row {
|
||||
.is-selected {
|
||||
background-color: rgb(0 255 255 / 0%);
|
||||
}
|
||||
|
||||
.current,
|
||||
.next,
|
||||
.prev {
|
||||
border: none;
|
||||
|
||||
.el-calendar-day {
|
||||
display: flex;
|
||||
height: 43px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
// border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1016
src/views/Home/Index9.vue
Normal file
1016
src/views/Home/Index9.vue
Normal file
File diff suppressed because it is too large
Load Diff
123
src/views/Home/components/table.vue
Normal file
123
src/views/Home/components/table.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
:header-cell-style="{
|
||||
height: '50px',
|
||||
fontFamily: `'微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important`,
|
||||
fontWeight: ' 700 !important',
|
||||
color: '#666666 !important'
|
||||
}"
|
||||
:cell-style="{
|
||||
height: '50px',
|
||||
fontFamily: `'微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important`,
|
||||
fontWeight: ' 400 !important',
|
||||
color: '#666666 !important'
|
||||
}"
|
||||
>
|
||||
<el-table-column prop="ranking" label="排名" width="180" />
|
||||
<el-table-column prop="name" label="商户名称" />
|
||||
<el-table-column prop="visit" label="访问量" />
|
||||
<el-table-column prop="quantity" label="订单量" />
|
||||
<el-table-column prop="cancel" label="取消量" />
|
||||
<el-table-column label="综合评分">
|
||||
<template #default="scope">
|
||||
<el-rate
|
||||
v-model="scope.row.pj"
|
||||
disabled
|
||||
allow-half
|
||||
show-score
|
||||
size="large"
|
||||
:score-template="scope.row.score"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成交金额(元)">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.zeng" style="display: flex; align-items: center; height: 12px">
|
||||
<span>{{ scope.row.money }}</span>
|
||||
<span class="icon"
|
||||
><Icon icon="ep:top" width="12" height="12" style="color: #f56c6c"
|
||||
/></span>
|
||||
<span style="color: #f7716f !important">{{ scope.row.zeng }}%</span>
|
||||
</div>
|
||||
<div v-else-if="scope.row.jian" style="display: flex; align-items: center; height: 12px">
|
||||
<span>{{ scope.row.money }}</span>
|
||||
<span class="icon"
|
||||
><Icon icon="ep:bottom" width="12" height="12" style="color: #19be6b"
|
||||
/></span>
|
||||
<span style="color: #73c883 !important">{{ scope.row.jian }}%</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>{{ scope.row.money }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="page">
|
||||
<el-pagination background layout="prev, pager, next" :total="tableDataTotal" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as TableApi from '@/api/design/table'
|
||||
|
||||
interface TableData {
|
||||
cancel: string
|
||||
ranking: string
|
||||
name: string
|
||||
visit: string
|
||||
quantity: string
|
||||
pj: string
|
||||
score: string
|
||||
money: string
|
||||
zeng: string
|
||||
jian: string
|
||||
}
|
||||
|
||||
const tableData = ref<TableData[]>()
|
||||
const tableDataTotal = ref(0)
|
||||
|
||||
onMounted(async () => {
|
||||
let { records } = await TableApi.getTableList(
|
||||
'1847537155101040642',
|
||||
{ pageNo: 1, pageSize: 10 },
|
||||
false
|
||||
)
|
||||
tableData.value = records.map((item) => {
|
||||
item.pj = Number(item.pj || 0)
|
||||
return item
|
||||
})
|
||||
tableDataTotal.value = records.length
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 将默认的星星颜色改为蓝色 */
|
||||
::v-deep .el-rate .el-rate__icon.is-active {
|
||||
color: #f56c6c !important;
|
||||
}
|
||||
|
||||
::v-deep .custom-rate .el-rate__item.active {
|
||||
color: #f56c6c; /* 选中的颜色 */
|
||||
}
|
||||
|
||||
::v-deep .el-rate .el-rate__item {
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
308
src/views/Home/echarts-data.ts
Normal file
308
src/views/Home/echarts-data.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
import { EChartsOption } from 'echarts'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
export const lineOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.monthlySales'),
|
||||
left: 'center'
|
||||
},
|
||||
xAxis: {
|
||||
data: [
|
||||
t('analysis.january'),
|
||||
t('analysis.february'),
|
||||
t('analysis.march'),
|
||||
t('analysis.april'),
|
||||
t('analysis.may'),
|
||||
t('analysis.june'),
|
||||
t('analysis.july'),
|
||||
t('analysis.august'),
|
||||
t('analysis.september'),
|
||||
t('analysis.october'),
|
||||
t('analysis.november'),
|
||||
t('analysis.december')
|
||||
],
|
||||
boundaryGap: false,
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
top: 80,
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
},
|
||||
padding: [5, 10]
|
||||
},
|
||||
yAxis: {
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: [t('analysis.estimate'), t('analysis.actual')],
|
||||
top: 50
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.estimate'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: [100, 120, 161, 134, 105, 160, 165, 114, 163, 185, 118, 123],
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: t('analysis.actual'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {},
|
||||
data: [120, 82, 91, 154, 162, 140, 145, 250, 134, 56, 99, 123],
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const pieOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.userAccessSource'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
data: [
|
||||
t('analysis.directAccess'),
|
||||
t('analysis.mailMarketing'),
|
||||
t('analysis.allianceAdvertising'),
|
||||
t('analysis.videoAdvertising'),
|
||||
t('analysis.searchEngines')
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.userAccessSource'),
|
||||
type: 'pie',
|
||||
radius: '55%',
|
||||
center: ['50%', '60%'],
|
||||
data: [
|
||||
{ value: 335, name: t('analysis.directAccess') },
|
||||
{ value: 310, name: t('analysis.mailMarketing') },
|
||||
{ value: 234, name: t('analysis.allianceAdvertising') },
|
||||
{ value: 135, name: t('analysis.videoAdvertising') },
|
||||
{ value: 1548, name: t('analysis.searchEngines') }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const barOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.weeklyUserActivity'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [
|
||||
t('analysis.monday'),
|
||||
t('analysis.tuesday'),
|
||||
t('analysis.wednesday'),
|
||||
t('analysis.thursday'),
|
||||
t('analysis.friday'),
|
||||
t('analysis.saturday'),
|
||||
t('analysis.sunday')
|
||||
],
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.activeQuantity'),
|
||||
data: [13253, 34235, 26321, 12340, 24643, 1322, 1324],
|
||||
type: 'bar'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const radarOption: EChartsOption = {
|
||||
legend: {
|
||||
data: [t('workplace.personal'), t('workplace.team')]
|
||||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
indicator: [
|
||||
{ name: t('workplace.quote'), max: 65 },
|
||||
{ name: t('workplace.contribution'), max: 160 },
|
||||
{ name: t('workplace.hot'), max: 300 },
|
||||
{ name: t('workplace.yield'), max: 130 },
|
||||
{ name: t('workplace.follow'), max: 100 }
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: `xxx${t('workplace.index')}`,
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: [42, 30, 20, 35, 80],
|
||||
name: t('workplace.personal')
|
||||
},
|
||||
{
|
||||
value: [50, 140, 290, 100, 90],
|
||||
name: t('workplace.team')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const wordOptions = {
|
||||
series: [
|
||||
{
|
||||
type: 'wordCloud',
|
||||
gridSize: 2,
|
||||
sizeRange: [12, 50],
|
||||
rotationRange: [-90, 90],
|
||||
shape: 'pentagon',
|
||||
width: 600,
|
||||
height: 400,
|
||||
drawOutOfBound: true,
|
||||
textStyle: {
|
||||
color: function () {
|
||||
return (
|
||||
'rgb(' +
|
||||
[
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160)
|
||||
].join(',') +
|
||||
')'
|
||||
)
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowColor: '#333'
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: 'Sam S Club',
|
||||
value: 10000,
|
||||
textStyle: {
|
||||
color: 'black'
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
color: 'red'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Macys',
|
||||
value: 6181
|
||||
},
|
||||
{
|
||||
name: 'Amy Schumer',
|
||||
value: 4386
|
||||
},
|
||||
{
|
||||
name: 'Jurassic World',
|
||||
value: 4055
|
||||
},
|
||||
{
|
||||
name: 'Charter Communications',
|
||||
value: 2467
|
||||
},
|
||||
{
|
||||
name: 'Chick Fil A',
|
||||
value: 2244
|
||||
},
|
||||
{
|
||||
name: 'Planet Fitness',
|
||||
value: 1898
|
||||
},
|
||||
{
|
||||
name: 'Pitch Perfect',
|
||||
value: 1484
|
||||
},
|
||||
{
|
||||
name: 'Express',
|
||||
value: 1112
|
||||
},
|
||||
{
|
||||
name: 'Home',
|
||||
value: 965
|
||||
},
|
||||
{
|
||||
name: 'Johnny Depp',
|
||||
value: 847
|
||||
},
|
||||
{
|
||||
name: 'Lena Dunham',
|
||||
value: 582
|
||||
},
|
||||
{
|
||||
name: 'Lewis Hamilton',
|
||||
value: 555
|
||||
},
|
||||
{
|
||||
name: 'KXAN',
|
||||
value: 550
|
||||
},
|
||||
{
|
||||
name: 'Mary Ellen Mark',
|
||||
value: 462
|
||||
},
|
||||
{
|
||||
name: 'Farrah Abraham',
|
||||
value: 366
|
||||
},
|
||||
{
|
||||
name: 'Rita Ora',
|
||||
value: 360
|
||||
},
|
||||
{
|
||||
name: 'Serena Williams',
|
||||
value: 282
|
||||
},
|
||||
{
|
||||
name: 'NCAA baseball tournament',
|
||||
value: 273
|
||||
},
|
||||
{
|
||||
name: 'Point Break',
|
||||
value: 265
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
55
src/views/Home/types.ts
Normal file
55
src/views/Home/types.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
export type WorkplaceTotal = {
|
||||
project: number
|
||||
access: number
|
||||
todo: number
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
name: string
|
||||
icon: string
|
||||
message: string
|
||||
personal: string
|
||||
time: Date | number | string
|
||||
}
|
||||
|
||||
export type Notice = {
|
||||
title: string
|
||||
type: string
|
||||
keys: string[]
|
||||
date: Date | number | string
|
||||
}
|
||||
|
||||
export type Shortcut = {
|
||||
name: string
|
||||
icon: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export type RadarData = {
|
||||
personal: number
|
||||
team: number
|
||||
max: number
|
||||
name: string
|
||||
}
|
||||
export type AnalysisTotalTypes = {
|
||||
users: number
|
||||
messages: number
|
||||
moneys: number
|
||||
shoppings: number
|
||||
}
|
||||
|
||||
export type UserAccessSource = {
|
||||
value: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type WeeklyUserActivity = {
|
||||
value: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type MonthlySales = {
|
||||
name: string
|
||||
estimate: number
|
||||
actual: number
|
||||
}
|
||||
0
src/views/Login/@/assets/imgs/sing.png
Normal file
0
src/views/Login/@/assets/imgs/sing.png
Normal file
242
src/views/Login/Login.vue
Normal file
242
src/views/Login/Login.vue
Normal file
@@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<div :class="prefixCls" class="relative h-[100%]">
|
||||
<div class="relative mx-auto h-full flex">
|
||||
<div
|
||||
v-if="!appStore.getMobile"
|
||||
:class="`${prefixCls}__left flex-1 bg-#409eff dark:bg-[var(--login-bg-color)] relative p-30px lt-xl:hidden overflow-x-hidden overflow-y-auto`"
|
||||
>
|
||||
<!-- 左上角的 logo + 系统标题 -->
|
||||
<div
|
||||
class="relative flex items-center text-white mt-30px"
|
||||
:style="{ marginTop: leftStyle.logo.mt }"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="mr-10px"
|
||||
:style="{ height: leftStyle.logo.height }"
|
||||
src="@/assets/imgs/logo_white.png"
|
||||
/>
|
||||
</div>
|
||||
<!-- 左边的背景图 + 欢迎语 -->
|
||||
<Transition
|
||||
appear
|
||||
enter-active-class="animate__animated animate__bounceInLeft"
|
||||
tag="div"
|
||||
>
|
||||
<div :style="{ marginTop: leftStyle.bottomText.textMt }">
|
||||
<div
|
||||
class="pos-relative w-100% overflow-hidden"
|
||||
:style="{ height: leftStyle.lottie.boxHeight }"
|
||||
>
|
||||
|
||||
<img
|
||||
class="pos-absolute"
|
||||
:style="{
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'contain',
|
||||
objectPosition: 'center center',
|
||||
}"
|
||||
src="@/assets/imgs/singBj2.png"
|
||||
alt="静态图片描述"
|
||||
/>
|
||||
<!-- <vue3-lottie
|
||||
class="pos-absolute"
|
||||
:style="{ left: leftStyle.lottie.left, top: leftStyle.lottie.top }"
|
||||
:animation-data="loginAnimationData"
|
||||
:height="leftStyle.lottie.height"
|
||||
:width="leftStyle.lottie.width"
|
||||
:auto-play="true"
|
||||
:loop="true"
|
||||
/> -->
|
||||
</div>
|
||||
<div
|
||||
class="w-100% flex items-center justify-center ml--20px gap-x-5px"
|
||||
:style="{ marginTop: leftStyle.bottomText.linkMT }"
|
||||
>
|
||||
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-center w-100%"
|
||||
:style="{ marginTop: leftStyle.bottomText.textMt }"
|
||||
>
|
||||
<div
|
||||
class="pos-relative overflow-hidden"
|
||||
:style="{ width: leftStyle.bottomText.width, height: leftStyle.bottomText.height }"
|
||||
>
|
||||
<span class="w-100% flex items-center justify-center ml--20px gap-x-5px" style="color:white;font-size:50px">综合监控系统</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<div
|
||||
class="relative flex-1 p-30px bg-#e3f0ff lt-sm:p-10px overflow-x-hidden overflow-y-auto"
|
||||
:class="`${prefixCls}__right`"
|
||||
>
|
||||
<!-- 右上角的主题、语言选择 -->
|
||||
<div
|
||||
class="relative z-2 flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end"
|
||||
>
|
||||
<!-- <div class="flex items-center at-2xl:hidden at-xl:hidden">
|
||||
<img alt="" class="mr-10px h-32px" src="@/assets/imgs/logo1.png" />
|
||||
</div> -->
|
||||
<div class="flex items-center justify-end space-x-10px h-48px">
|
||||
<ThemeSwitch />
|
||||
<LocaleDropdown class="dark:text-white lt-xl:text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右边的登录界面 -->
|
||||
<Transition appear enter-active-class="animate__animated animate__bounceInRight">
|
||||
<div
|
||||
:class="newClass"
|
||||
class="pos-relative m-auto z-2 h-[calc(100%-60px)] w-[100%] flex items-center at-2xl:max-w-500px at-lg:max-w-500px at-md:max-w-500px at-xl:max-w-500px"
|
||||
>
|
||||
<div class="bg-#fff dark:bg-[var(--login-bg-color)] b-rounded-20px px-20px">
|
||||
<!-- 账号登录 -->
|
||||
<LoginForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||
<!-- 手机登录 -->
|
||||
<MobileForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||
<!-- 二维码登录 -->
|
||||
<QrCodeForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||
<!-- 三方登录 -->
|
||||
<SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<div
|
||||
class="pos-fixed bottom-0px left-50% lt-md:left-0px lt-sm:left-0px lt-xl:left-0px lt-xl:left-0px z-1"
|
||||
>
|
||||
<img class="w-138px block" src="@/assets/imgs/login/login_right_bg_1.png" alt="" />
|
||||
</div>
|
||||
<div class="pos-fixed right--38px top--23px">
|
||||
<img class="w-160px block" src="@/assets/imgs/login/login_right_bg_2.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
import { LoginForm, MobileForm, QrCodeForm, SSOLoginVue } from './components'
|
||||
import * as loginAnimation from '@/assets/json/login_left.json'
|
||||
|
||||
defineOptions({ name: 'Login' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('login')
|
||||
const windowSize = useWindowSize()
|
||||
const loginAnimationData = ref(loginAnimation['default'])
|
||||
|
||||
const leftStyle = computed(() => {
|
||||
const winW = windowSize.width.value
|
||||
const leftW = winW / 2
|
||||
|
||||
const logo = {
|
||||
height: leftW / 16.5 + 'px',
|
||||
mt: leftW / 31.9 + 'px'
|
||||
}
|
||||
|
||||
const lottieMagn = leftW / 435
|
||||
const lottie = {
|
||||
boxHeight: leftW / 2.39 + 'px',
|
||||
height: 335 * lottieMagn,
|
||||
width: 434 * lottieMagn,
|
||||
left: -(leftW / 38) + 'px',
|
||||
top: -(leftW / 5.3) + 'px'
|
||||
}
|
||||
|
||||
const bottomText = {
|
||||
width: leftW / 1.54 + 'px',
|
||||
height: leftW / 8.68 + 'px',
|
||||
left: -(leftW / 31.54) + 'px',
|
||||
linkMT: leftW / 23.87 + 'px',
|
||||
textMt: leftW / 16.2 + 'px'
|
||||
}
|
||||
|
||||
return { lottie, bottomText, logo }
|
||||
})
|
||||
|
||||
const newClass = computed(() => {
|
||||
const mobile = appStore.getMobile ? 'is-mobile' : ''
|
||||
const toggle = appStore.getFullscreen ? 'is-toggle' : ''
|
||||
return toggle ? toggle : mobile
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$prefix-cls: #{$namespace}-login;
|
||||
|
||||
.#{$prefix-cls} {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.#{$prefix-cls}__left {
|
||||
background-image: url('@/assets/imgs/singBj1.png');
|
||||
background-repeat: no-repeat; // 背景图片不重复
|
||||
background-size: cover; // 背景图片覆盖整个容器
|
||||
background-position: center center; // 背景图片居中显示
|
||||
}
|
||||
|
||||
|
||||
|
||||
.margin-left-50 {
|
||||
margin-left: 50%;
|
||||
}
|
||||
|
||||
.is-mobile {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(2.5, 1.8);
|
||||
}
|
||||
|
||||
.is-toggle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(0.7, 0.6);
|
||||
}
|
||||
|
||||
.is-mobile-bj {
|
||||
position: absolute;
|
||||
width: 50vw !important;
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.is-mobile-bj img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.is-mobile-bj .bjOne {
|
||||
z-index: 1; /* 底层 */
|
||||
}
|
||||
.is-mobile-bj .bjTwo {
|
||||
z-index: 2; /* 上层(值比 bjOne 大即可) */
|
||||
width: 80%;
|
||||
height: auto;
|
||||
float: left;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%,-45%);
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
419
src/views/Login/SocialLogin.vue
Normal file
419
src/views/Login/SocialLogin.vue
Normal file
@@ -0,0 +1,419 @@
|
||||
<template>
|
||||
<div
|
||||
:class="prefixCls"
|
||||
class="relative h-[100%] lt-xl:bg-[var(--login-bg-color)] lt-md:px-10px lt-sm:px-10px lt-xl:px-10px"
|
||||
v-if="$route.query.showLogin"
|
||||
>
|
||||
<div class="relative mx-auto h-full flex">
|
||||
<div
|
||||
:class="`${prefixCls}__left flex-1 bg-#409eff dark:bg-[var(--login-bg-color)] relative p-30px lt-xl:hidden overflow-x-hidden overflow-y-auto`"
|
||||
>
|
||||
<!-- 左上角的 logo + 系统标题 -->
|
||||
<div
|
||||
class="relative flex items-center text-white mt-30px"
|
||||
:style="{ marginTop: leftStyle.logo.mt }"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="mr-10px"
|
||||
:style="{ height: leftStyle.logo.height }"
|
||||
src="@/assets/imgs/logo_white.png"
|
||||
/>
|
||||
</div>
|
||||
<!-- 左边的背景图 + 欢迎语 -->
|
||||
<Transition
|
||||
appear
|
||||
enter-active-class="animate__animated animate__bounceInLeft"
|
||||
tag="div"
|
||||
>
|
||||
<div :style="{ marginTop: leftStyle.bottomText.textMt }">
|
||||
<div
|
||||
class="pos-relative w-100% overflow-hidden"
|
||||
:style="{ height: leftStyle.lottie.boxHeight }"
|
||||
>
|
||||
|
||||
<img
|
||||
class="pos-absolute"
|
||||
:style="{
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'contain',
|
||||
objectPosition: 'center center',
|
||||
}"
|
||||
src="@/assets/imgs/singBj2.png"
|
||||
alt="静态图片描述"
|
||||
/>
|
||||
<!-- <vue3-lottie
|
||||
class="pos-absolute"
|
||||
:style="{ left: leftStyle.lottie.left, top: leftStyle.lottie.top }"
|
||||
:animation-data="loginAnimationData"
|
||||
:height="leftStyle.lottie.height"
|
||||
:width="leftStyle.lottie.width"
|
||||
:auto-play="true"
|
||||
:loop="true"
|
||||
/> -->
|
||||
</div>
|
||||
<div
|
||||
class="w-100% flex items-center justify-center ml--20px gap-x-5px"
|
||||
:style="{ marginTop: leftStyle.bottomText.linkMT }"
|
||||
>
|
||||
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-center w-100%"
|
||||
:style="{ marginTop: leftStyle.bottomText.textMt }"
|
||||
>
|
||||
<div
|
||||
class="pos-relative overflow-hidden"
|
||||
:style="{ width: leftStyle.bottomText.width, height: leftStyle.bottomText.height }"
|
||||
>
|
||||
<span class="w-100% flex items-center justify-center ml--20px gap-x-5px" style="color:white;font-size:50px">综合监控系统</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<div class="relative flex-1 p-30px bg-#e3f0ff lt-sm:p-10px overflow-x-hidden overflow-y-auto">
|
||||
<!-- 右上角的主题、语言选择 -->
|
||||
<div
|
||||
class="relative z-2 flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end"
|
||||
>
|
||||
<div class="flex items-center at-2xl:hidden at-xl:hidden">
|
||||
<img alt="" class="mr-10px h-32px" src="@/assets/imgs/logo.png" />
|
||||
</div>
|
||||
<div class="flex items-center justify-end space-x-10px">
|
||||
<ThemeSwitch />
|
||||
<LocaleDropdown class="dark:text-white lt-xl:text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右边的登录界面 -->
|
||||
<Transition appear enter-active-class="animate__animated animate__bounceInRight">
|
||||
<div
|
||||
class="pos-relative z-2 m-auto h-[calc(100%-60px)] w-[100%] flex items-center at-2xl:max-w-500px at-lg:max-w-500px at-md:max-w-500px at-xl:max-w-500px"
|
||||
>
|
||||
<!-- 账号登录 -->
|
||||
<div class="bg-#fff dark:bg-[var(--login-bg-color)] b-rounded-20px px-40px py-40px">
|
||||
<el-form
|
||||
v-show="getShow"
|
||||
ref="formLogin"
|
||||
:model="loginData.loginForm"
|
||||
:rules="LoginRules"
|
||||
class="login-form"
|
||||
label-position="top"
|
||||
label-width="120px"
|
||||
size="large"
|
||||
>
|
||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item v-if="loginData.tenantEnable" prop="tenantName">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
link
|
||||
type="primary"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.username"
|
||||
:placeholder="t('login.usernamePlaceholder')"
|
||||
:prefix-icon="iconAvatar"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.password"
|
||||
:placeholder="t('login.passwordPlaceholder')"
|
||||
:prefix-icon="iconLock"
|
||||
show-password
|
||||
type="password"
|
||||
@keyup.enter="getCode()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="24"
|
||||
style="
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
margin-top: -20px;
|
||||
margin-bottom: -20px;
|
||||
"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-row justify="space-between" style="width: 100%">
|
||||
<el-col :span="6">
|
||||
<el-checkbox v-model="loginData.loginForm.rememberMe">
|
||||
{{ t('login.remember') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col :offset="6" :span="12">
|
||||
<el-link style="float: right" type="primary">{{
|
||||
t('login.forgetPassword')
|
||||
}}</el-link>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
:title="t('login.login')"
|
||||
class="w-[100%]"
|
||||
type="primary"
|
||||
@click="getCode()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
ref="verify"
|
||||
:captchaType="captchaType"
|
||||
:imgSize="{ width: '400px', height: '200px' }"
|
||||
mode="pop"
|
||||
@success="handleLogin"
|
||||
/>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<div
|
||||
class="pos-fixed bottom-0px left-50% lt-md:left-0px lt-sm:left-0px lt-xl:left-0px lt-xl:left-0px z-1"
|
||||
>
|
||||
<img class="w-138px block" src="@/assets/imgs/login/login_right_bg_1.png" alt="" />
|
||||
</div>
|
||||
<div class="pos-fixed right--38px top--23px">
|
||||
<img class="w-160px block" src="@/assets/imgs/login/login_right_bg_2.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElLoading } from 'element-plus'
|
||||
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
|
||||
import * as LoginApi from '@/api/login'
|
||||
import * as authUtil from '@/utils/auth'
|
||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
import { LoginStateEnum, useFormValid, useLoginState } from './components/useLogin'
|
||||
import LoginFormTitle from './components/LoginFormTitle.vue'
|
||||
import router from '@/router'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import * as loginAnimation from '@/assets/json/login_left.json'
|
||||
|
||||
defineOptions({ name: 'SocialLogin' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
|
||||
const appStore = useAppStore()
|
||||
const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('login')
|
||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||
const iconLock = useIcon({ icon: 'ep:lock' })
|
||||
const formLogin = ref<any>()
|
||||
const { validForm } = useFormValid(formLogin)
|
||||
const { getLoginState } = useLoginState()
|
||||
const { push } = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
const loginLoading = ref(false)
|
||||
const verify = ref()
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||
const windowSize = useWindowSize()
|
||||
const loginAnimationData = ref(loginAnimation['default'])
|
||||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||
|
||||
const LoginRules = {
|
||||
tenantName: [required],
|
||||
username: [required],
|
||||
password: [required]
|
||||
}
|
||||
const loginData = reactive({
|
||||
isShowPassword: false,
|
||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE !== 'false',
|
||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE !== 'false',
|
||||
loginForm: {
|
||||
tenantName: '000000',
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
captchaVerification: '',
|
||||
rememberMe: false
|
||||
}
|
||||
})
|
||||
|
||||
const leftStyle = computed(() => {
|
||||
const winW = windowSize.width.value
|
||||
const leftW = winW / 2
|
||||
|
||||
const logo = {
|
||||
height: leftW / 16.5 + 'px',
|
||||
mt: leftW / 31.9 + 'px'
|
||||
}
|
||||
|
||||
const lottieMagn = leftW / 435
|
||||
const lottie = {
|
||||
boxHeight: leftW / 2.39 + 'px',
|
||||
height: 335 * lottieMagn,
|
||||
width: 434 * lottieMagn,
|
||||
left: -(leftW / 38) + 'px',
|
||||
top: -(leftW / 5.3) + 'px'
|
||||
}
|
||||
|
||||
const bottomText = {
|
||||
width: leftW / 1.54 + 'px',
|
||||
height: leftW / 8.68 + 'px',
|
||||
left: -(leftW / 31.54) + 'px',
|
||||
linkMT: leftW / 23.87 + 'px',
|
||||
textMt: leftW / 16.2 + 'px'
|
||||
}
|
||||
|
||||
return { lottie, bottomText, logo }
|
||||
})
|
||||
|
||||
// 获取验证码
|
||||
const getCode = async () => {
|
||||
// 情况一,未开启:则直接登录
|
||||
if (!loginData.captchaEnable) {
|
||||
await handleLogin({})
|
||||
} else {
|
||||
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
|
||||
// 弹出验证码
|
||||
verify.value.show()
|
||||
}
|
||||
}
|
||||
//获取租户ID
|
||||
const getTenantId = async () => {
|
||||
if (loginData.tenantEnable) {
|
||||
const res = await LoginApi.getTenantIdByName(loginData.loginForm.tenantName)
|
||||
authUtil.setTenantId(res)
|
||||
}
|
||||
}
|
||||
// 记住我
|
||||
const getCookie = () => {
|
||||
const loginForm = authUtil.getLoginForm()
|
||||
if (loginForm) {
|
||||
loginData.loginForm = {
|
||||
...loginData.loginForm,
|
||||
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
|
||||
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
|
||||
rememberMe: loginForm.rememberMe ? true : false,
|
||||
tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName
|
||||
}
|
||||
}
|
||||
}
|
||||
const loading = ref() // ElLoading.service 返回的实例
|
||||
|
||||
// tricky: 配合LoginForm.vue中redirectUri需要对参数进行encode,需要在回调后进行decode
|
||||
function getUrlValue(key: string): string {
|
||||
const url = new URL(decodeURIComponent(location.href))
|
||||
return url.searchParams.get(key) ?? ''
|
||||
}
|
||||
|
||||
// 尝试登录: 当账号已经绑定,socialLogin会直接获得token
|
||||
const tryLogin = async () => {
|
||||
try {
|
||||
debugger
|
||||
const type = getUrlValue('type')
|
||||
const redirect = getUrlValue('redirect')
|
||||
const code = route?.query?.code as string
|
||||
const state = route?.query?.state as string
|
||||
|
||||
const res = await LoginApi.socialLogin(type, code, state)
|
||||
authUtil.setToken(res)
|
||||
|
||||
router.push({ path: redirect || '/' })
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
// 登录
|
||||
const handleLogin = async (params) => {
|
||||
loginLoading.value = true
|
||||
try {
|
||||
await getTenantId()
|
||||
const data = await validForm()
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
|
||||
let redirect = getUrlValue('redirect')
|
||||
|
||||
const type = getUrlValue('type')
|
||||
const code = route?.query?.code as string
|
||||
const state = route?.query?.state as string
|
||||
|
||||
const res = await LoginApi.login({
|
||||
// 账号密码登录
|
||||
username: loginData.loginForm.username,
|
||||
password: loginData.loginForm.password,
|
||||
captchaVerification: params.captchaVerification,
|
||||
// 社交登录
|
||||
socialCode: code,
|
||||
socialState: state,
|
||||
socialType: type
|
||||
})
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
loading.value = ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在加载系统中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
if (loginData.loginForm.rememberMe) {
|
||||
authUtil.setLoginForm(loginData.loginForm)
|
||||
} else {
|
||||
authUtil.removeLoginForm()
|
||||
}
|
||||
authUtil.setToken(res)
|
||||
if (!redirect) {
|
||||
redirect = '/'
|
||||
}
|
||||
// 判断是否为SSO登录
|
||||
if (redirect.indexOf('sso') !== -1) {
|
||||
window.location.href = window.location.href.replace('/login?redirect=', '')
|
||||
} else {
|
||||
push({ path: redirect || permissionStore.addRouters[0].path })
|
||||
}
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
loading.value.close()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCookie()
|
||||
tryLogin()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$prefix-cls: #{$namespace}-login;
|
||||
|
||||
.#{$prefix-cls} {
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
388
src/views/Login/components/LoginForm.vue
Normal file
388
src/views/Login/components/LoginForm.vue
Normal file
@@ -0,0 +1,388 @@
|
||||
<template>
|
||||
<el-form
|
||||
v-show="getShow"
|
||||
ref="formLogin"
|
||||
:model="loginData.loginForm"
|
||||
:rules="LoginRules"
|
||||
class="login-form login-form-default"
|
||||
label-position="top"
|
||||
label-width="120px"
|
||||
size="large"
|
||||
>
|
||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<LoginFormTitle style="width: 100%"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
link
|
||||
type="primary"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.username"
|
||||
:placeholder="t('login.usernamePlaceholder')"
|
||||
:prefix-icon="iconAvatar"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.password"
|
||||
:placeholder="t('login.passwordPlaceholder')"
|
||||
:prefix-icon="iconLock"
|
||||
show-password
|
||||
type="password"
|
||||
@keyup.enter="getCode()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="24"
|
||||
style="padding-right: 10px; padding-left: 10px; margin-top: -20px; margin-bottom: -20px"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-row justify="space-between" style="width: 100%">
|
||||
<el-col :span="6">
|
||||
<el-checkbox v-model="loginData.loginForm.rememberMe">
|
||||
{{ t('login.remember') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col :offset="6" :span="12">
|
||||
<el-link style="float: right" type="primary">{{ t('login.forgetPassword') }}</el-link>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px" class="login-btn-col">
|
||||
<el-form-item>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
:title="t('login.login')"
|
||||
class="w-[100%]"
|
||||
type="primary"
|
||||
@click="getCode()"
|
||||
/>
|
||||
<div v-if="loginType == 'easy'" class="w-100% mt-6px flex justify-end">
|
||||
<el-dropdown @command="easyCommand">
|
||||
<div class="mt--2px cursor-pointer">
|
||||
<el-text type="primary">
|
||||
<span class="text-14px">{{ t('login.otherLogin') }}</span>
|
||||
<Icon :size="16" icon="iconamoon:arrow-down-2-light"/>
|
||||
</el-text>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<template v-for="item in easyDropdownList" :key="item.label">
|
||||
<el-dropdown-item :command="item">
|
||||
<Icon :size="16" :icon="item.icon"/>
|
||||
<span>{{ item.label }}</span>
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
ref="verify"
|
||||
:captchaType="captchaType"
|
||||
:imgSize="{ width: '400px', height: '200px' }"
|
||||
mode="pop"
|
||||
@success="handleLogin"
|
||||
/>
|
||||
<template v-if="!loginType">
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<!-- <el-form-item>
|
||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||
<el-col :span="12">
|
||||
<XButton
|
||||
:title="t('login.btnMobile')"
|
||||
class="w-[100%]"
|
||||
@click="setLoginState(LoginStateEnum.MOBILE)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<XButton
|
||||
:title="t('login.btnQRCode')"
|
||||
class="w-[100%]"
|
||||
@click="setLoginState(LoginStateEnum.QR_CODE)"
|
||||
/>
|
||||
</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-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<div class="w-[100%] flex justify-center">
|
||||
<Icon
|
||||
v-for="(item, key) in socialList"
|
||||
:key="key"
|
||||
:icon="item.icon"
|
||||
:size="50"
|
||||
class="anticon cursor-pointer"
|
||||
color="#999"
|
||||
@click="doSocialLogin(item.type)"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {ElLoading} from 'element-plus'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import type {RouteLocationNormalizedLoaded} from 'vue-router'
|
||||
|
||||
import {useIcon} from '@/hooks/web/useIcon'
|
||||
|
||||
import * as authUtil from '@/utils/auth'
|
||||
import {usePermissionStore} from '@/store/modules/permission'
|
||||
import * as LoginApi from '@/api/login'
|
||||
import {LoginStateEnum, useFormValid, useLoginState} from './useLogin'
|
||||
|
||||
defineOptions({name: 'LoginForm'})
|
||||
|
||||
interface Props {
|
||||
loginType?: 'easy'
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const {t} = useI18n()
|
||||
const message = useMessage()
|
||||
const iconHouse = useIcon({icon: 'ep:house'})
|
||||
const iconAvatar = useIcon({icon: 'ep:avatar'})
|
||||
const iconLock = useIcon({icon: 'ep:lock'})
|
||||
const formLogin = ref()
|
||||
const {validForm} = useFormValid(formLogin)
|
||||
const {setLoginState, getLoginState} = useLoginState()
|
||||
const {currentRoute, push} = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
const redirect = ref<string>('')
|
||||
const loginLoading = ref(false)
|
||||
const verify = ref()
|
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||
|
||||
const LoginRules = {
|
||||
tenantName: [required],
|
||||
username: [required],
|
||||
password: [required]
|
||||
}
|
||||
const loginData = reactive({
|
||||
isShowPassword: false,
|
||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||
loginForm: {
|
||||
tenantName: '000000',
|
||||
username: import.meta.env.VITE_LOGIN_USERNAME || '',
|
||||
password: import.meta.env.VITE_LOGIN_PASSWORD || '',
|
||||
captchaVerification: '',
|
||||
rememberMe: false
|
||||
}
|
||||
})
|
||||
|
||||
const socialList = [
|
||||
// { icon: 'ant-design:wechat-filled', type: 30 },
|
||||
// { icon: 'ant-design:dingtalk-circle-filled', type: 20 },
|
||||
// { icon: 'ant-design:github-filled', type: 0 },
|
||||
// { icon: 'ant-design:alipay-circle-filled', type: 0 },
|
||||
{icon: 'bx:planet', type: 100},
|
||||
]
|
||||
|
||||
const easyDropdownList = [
|
||||
{
|
||||
label: t('login.btnMobile'),
|
||||
icon: 'ant-design:phone-twotone',
|
||||
type: LoginStateEnum.MOBILE,
|
||||
code: 'page'
|
||||
},
|
||||
{
|
||||
label: t('login.btnQRCode'),
|
||||
icon: 'ant-design:qrcode-outlined',
|
||||
type: LoginStateEnum.QR_CODE,
|
||||
code: 'page'
|
||||
},
|
||||
{label: t('login.btnWechat'), icon: 'ant-design:wechat-filled', type: 30, code: 'url'},
|
||||
{
|
||||
label: t('login.btnDingtalk'),
|
||||
icon: 'ant-design:dingtalk-circle-filled',
|
||||
type: 20,
|
||||
code: 'url'
|
||||
},
|
||||
{label: t('login.btnGitHub'), icon: 'ant-design:github-filled', type: 0, code: 'url'},
|
||||
{label: t('login.btnAlipay'), icon: 'ant-design:alipay-circle-filled', type: 0, code: 'url'}
|
||||
]
|
||||
|
||||
const easyCommand = (row) => {
|
||||
if (row.code == 'page') setLoginState(row.type)
|
||||
else if (row.code == 'url') doSocialLogin(row.type)
|
||||
}
|
||||
|
||||
// 获取验证码
|
||||
const getCode = async () => {
|
||||
// 情况一,未开启:则直接登录
|
||||
if (loginData.captchaEnable === 'false') {
|
||||
await handleLogin({})
|
||||
} else {
|
||||
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
|
||||
// 弹出验证码
|
||||
verify.value.show()
|
||||
}
|
||||
}
|
||||
// 获取租户 ID
|
||||
const getTenantId = async () => {
|
||||
if (loginData.tenantEnable === 'true') {
|
||||
const res = await LoginApi.getTenantIdByName(loginData.loginForm.tenantName)
|
||||
authUtil.setTenantId(res)
|
||||
}
|
||||
}
|
||||
// 记住我
|
||||
const getCookie = () => {
|
||||
const loginForm = authUtil.getLoginForm()
|
||||
if (loginForm) {
|
||||
loginData.loginForm = {
|
||||
...loginData.loginForm,
|
||||
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
|
||||
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
|
||||
rememberMe: loginForm.rememberMe ? true : false,
|
||||
tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName
|
||||
}
|
||||
}
|
||||
}
|
||||
// 根据域名,获得租户信息
|
||||
const getTenantByWebsite = async () => {
|
||||
const website = location.host
|
||||
const res = await LoginApi.getTenantByWebsite(website)
|
||||
if (res) {
|
||||
loginData.loginForm.tenantName = res.name
|
||||
authUtil.setTenantId(res.id)
|
||||
}
|
||||
}
|
||||
const loading = ref() // ElLoading.service 返回的实例
|
||||
// 登录
|
||||
const handleLogin = async (params) => {
|
||||
loginLoading.value = true
|
||||
try {
|
||||
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
|
||||
loading.value = ElLoading.service({
|
||||
lock: true,
|
||||
text: '登录中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
|
||||
await getTenantId()
|
||||
loginData.loginForm.captchaVerification = params.captchaVerification
|
||||
const res = await LoginApi.login(loginData.loginForm)
|
||||
if (!res) return
|
||||
|
||||
if (loginData.loginForm.rememberMe) {
|
||||
authUtil.setLoginForm(loginData.loginForm)
|
||||
} else {
|
||||
authUtil.removeLoginForm()
|
||||
}
|
||||
authUtil.setToken(res)
|
||||
if (!redirect.value) {
|
||||
redirect.value = '/'
|
||||
}
|
||||
// 判断是否为SSO登录
|
||||
if (redirect.value.indexOf('sso') !== -1) {
|
||||
window.location.href = window.location.href.replace('/login?redirect=', '')
|
||||
} else {
|
||||
push({path: redirect.value || permissionStore.addRouters[0].path})
|
||||
}
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
loading.value?.close()
|
||||
}
|
||||
}
|
||||
|
||||
// 社交登录
|
||||
const doSocialLogin = async (type: number) => {
|
||||
if (type === 0) {
|
||||
message.error('此方式未配置')
|
||||
} else {
|
||||
loginLoading.value = true
|
||||
if (loginData.tenantEnable === 'true') {
|
||||
// 尝试先通过 tenantName 获取租户
|
||||
await getTenantId()
|
||||
// 如果获取不到,则需要弹出提示,进行处理
|
||||
if (!authUtil.getTenantId()) {
|
||||
await message.prompt('请输入租户名称', t('common.reminder')).then(async ({value}) => {
|
||||
const res = await LoginApi.getTenantIdByName(value)
|
||||
authUtil.setTenantId(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
// 计算 redirectUri
|
||||
// tricky: type、redirect需要先encode一次,否则钉钉回调会丢失。
|
||||
// 配合 Login/SocialLogin.vue#getUrlValue() 使用
|
||||
const redirectUri =
|
||||
location.origin +
|
||||
'/social-login?' +
|
||||
encodeURIComponent(`type=${type}&redirect=${redirect.value || '/'}`)
|
||||
|
||||
// 进行跳转
|
||||
const res = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
|
||||
window.location.href = res
|
||||
}
|
||||
}
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
redirect.value = route?.query?.redirect as string
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
onMounted(() => {
|
||||
if(redirect.value && import.meta.env.VITE_DEFAULT_SSO =='true'){
|
||||
//默认
|
||||
doSocialLogin(100)
|
||||
}
|
||||
getCookie()
|
||||
getTenantByWebsite()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.anticon) {
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.login-code {
|
||||
float: right;
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 100px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
26
src/views/Login/components/LoginFormTitle.vue
Normal file
26
src/views/Login/components/LoginFormTitle.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<h2 class="enter-x mb-3 text-center text-2xl font-bold xl:text-center xl:text-3xl form-title">
|
||||
{{ getFormTitle }}
|
||||
</h2>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||
|
||||
defineOptions({ name: 'LoginFormTitle' })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getLoginState } = useLoginState()
|
||||
|
||||
const getFormTitle = computed(() => {
|
||||
const titleObj = {
|
||||
[LoginStateEnum.RESET_PASSWORD]: t('sys.login.forgetFormTitle'),
|
||||
[LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'),
|
||||
[LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'),
|
||||
[LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'),
|
||||
[LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'),
|
||||
[LoginStateEnum.SSO]: t('sys.login.ssoFormTitle')
|
||||
}
|
||||
return titleObj[unref(getLoginState)]
|
||||
})
|
||||
</script>
|
||||
231
src/views/Login/components/MobileForm.vue
Normal file
231
src/views/Login/components/MobileForm.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<el-form
|
||||
v-show="getShow"
|
||||
ref="formSmsLogin"
|
||||
:model="loginData.loginForm"
|
||||
:rules="rules"
|
||||
class="login-form login-form-mobile"
|
||||
label-position="top"
|
||||
label-width="120px"
|
||||
size="large"
|
||||
>
|
||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||
<!-- 租户名 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
type="primary"
|
||||
link
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 手机号 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="mobileNumber">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.mobileNumber"
|
||||
:placeholder="t('login.mobileNumberPlaceholder')"
|
||||
:prefix-icon="iconCellphone"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 验证码 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item prop="code">
|
||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||
<el-col :span="24">
|
||||
<el-input
|
||||
v-model="loginData.loginForm.code"
|
||||
:placeholder="t('login.codePlaceholder')"
|
||||
:prefix-icon="iconCircleCheck"
|
||||
>
|
||||
<!-- <el-button class="w-[100%]"> -->
|
||||
<template #append>
|
||||
<span
|
||||
v-if="mobileCodeTimer <= 0"
|
||||
class="getMobileCode"
|
||||
style="cursor: pointer"
|
||||
@click="getSmsCode"
|
||||
>
|
||||
{{ t('login.getSmsCode') }}
|
||||
</span>
|
||||
<span v-if="mobileCodeTimer > 0" class="getMobileCode" style="cursor: pointer">
|
||||
{{ mobileCodeTimer }}秒后可重新获取
|
||||
</span>
|
||||
</template>
|
||||
</el-input>
|
||||
<!-- </el-button> -->
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 登录按钮 / 返回按钮 -->
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px" class="login-btn-col">
|
||||
<el-form-item>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
:title="t('login.login')"
|
||||
class="w-[100%]"
|
||||
type="primary"
|
||||
@click="signIn()"
|
||||
/>
|
||||
<div class="w-100% flex justify-end mt-5px mr-2px">
|
||||
<el-button link type="primary" @click="handleBackLogin()">{{
|
||||
t('login.backLogin')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-form-item>
|
||||
<XButton
|
||||
:loading="loginLoading"
|
||||
:title="t('login.backLogin')"
|
||||
class="w-[100%]"
|
||||
@click="handleBackLogin()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
|
||||
import { setTenantId, setToken } from '@/utils/auth'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { getTenantIdByName, sendSmsCode, smsLogin } from '@/api/login'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||
import { ElLoading } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'MobileForm' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const permissionStore = usePermissionStore()
|
||||
const { currentRoute, push } = useRouter()
|
||||
const formSmsLogin = ref()
|
||||
const loginLoading = ref(false)
|
||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||
const iconCellphone = useIcon({ icon: 'ep:cellphone' })
|
||||
const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
|
||||
const { validForm } = useFormValid(formSmsLogin)
|
||||
const { handleBackLogin, getLoginState } = useLoginState()
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.MOBILE)
|
||||
|
||||
const rules = {
|
||||
tenantName: [required],
|
||||
mobileNumber: [required],
|
||||
code: [required]
|
||||
}
|
||||
const loginData = reactive({
|
||||
codeImg: '',
|
||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||
token: '',
|
||||
loading: {
|
||||
signIn: false
|
||||
},
|
||||
loginForm: {
|
||||
uuid: '',
|
||||
tenantName: '000000',
|
||||
mobileNumber: '',
|
||||
code: ''
|
||||
}
|
||||
})
|
||||
const smsVO = reactive({
|
||||
smsCode: {
|
||||
mobile: '',
|
||||
scene: 21
|
||||
},
|
||||
loginSms: {
|
||||
mobile: '',
|
||||
code: ''
|
||||
}
|
||||
})
|
||||
const mobileCodeTimer = ref(0)
|
||||
const redirect = ref<string>('')
|
||||
const getSmsCode = async () => {
|
||||
await getTenantId()
|
||||
smsVO.smsCode.mobile = loginData.loginForm.mobileNumber
|
||||
await sendSmsCode(smsVO.smsCode).then(async () => {
|
||||
message.success(t('login.SmsSendMsg'))
|
||||
// 设置倒计时
|
||||
mobileCodeTimer.value = 60
|
||||
let msgTimer = setInterval(() => {
|
||||
mobileCodeTimer.value = mobileCodeTimer.value - 1
|
||||
if (mobileCodeTimer.value <= 0) {
|
||||
clearInterval(msgTimer)
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
redirect.value = route?.query?.redirect as string
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
// 获取租户 ID
|
||||
const getTenantId = async () => {
|
||||
if (loginData.tenantEnable === 'true') {
|
||||
const res = await getTenantIdByName(loginData.loginForm.tenantName)
|
||||
setTenantId(res)
|
||||
}
|
||||
}
|
||||
// 登录
|
||||
const signIn = async () => {
|
||||
await getTenantId()
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在加载系统中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
loginLoading.value = true
|
||||
smsVO.loginSms.mobile = loginData.loginForm.mobileNumber
|
||||
smsVO.loginSms.code = loginData.loginForm.code
|
||||
await smsLogin(smsVO.loginSms)
|
||||
.then(async (res) => {
|
||||
setToken(res)
|
||||
if (!redirect.value) {
|
||||
redirect.value = '/'
|
||||
}
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loginLoading.value = false
|
||||
setTimeout(() => {
|
||||
const loadingInstance = ElLoading.service()
|
||||
loadingInstance.close()
|
||||
}, 400)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.anticon) {
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.smsbtn {
|
||||
margin-top: 33px;
|
||||
}
|
||||
</style>
|
||||
34
src/views/Login/components/QrCodeForm.vue
Normal file
34
src/views/Login/components/QrCodeForm.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<el-row
|
||||
class="login-form login-form-qrcode"
|
||||
v-show="getShow"
|
||||
style="margin-right: -10px; margin-left: -10px"
|
||||
>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
</el-col>
|
||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||
<el-card class="mb-10px text-center" shadow="hover">
|
||||
<Qrcode :logo="logoImg" />
|
||||
<div class="">{{ t('login.qrcode') }}</div>
|
||||
</el-card>
|
||||
<div class="w-100% flex justify-end mt-5px mr-2px">
|
||||
<el-button link type="primary" @click="handleBackLogin()">{{
|
||||
t('login.backLogin')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import logoImg from '@/assets/imgs/logo_min.png'
|
||||
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||
|
||||
defineOptions({ name: 'QrCodeForm' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const { handleBackLogin, getLoginState } = useLoginState()
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.QR_CODE)
|
||||
</script>
|
||||
199
src/views/Login/components/SSOLogin.vue
Normal file
199
src/views/Login/components/SSOLogin.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div v-show="ssoVisible" class="form-cont">
|
||||
<!-- 应用名 -->
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
<el-tabs class="form" style="float: none" value="uname">
|
||||
<el-tab-pane :label="client.name" name="uname" />
|
||||
</el-tabs>
|
||||
<div>
|
||||
<el-form :model="formData" class="login-form">
|
||||
<!-- 授权范围的选择 -->
|
||||
此第三方应用请求获得以下权限:
|
||||
<el-form-item prop="scopes">
|
||||
<el-checkbox-group v-model="formData.scopes">
|
||||
<el-checkbox
|
||||
v-for="scope in queryParams.scopes"
|
||||
:key="scope"
|
||||
:label="scope"
|
||||
style="display: block; margin-bottom: -10px"
|
||||
>
|
||||
{{ formatScope(scope) }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<!-- 下方的登录按钮 -->
|
||||
<el-form-item class="w-1/1">
|
||||
<el-button
|
||||
:loading="formLoading"
|
||||
class="w-6/10"
|
||||
type="primary"
|
||||
@click.prevent="handleAuthorize(true)"
|
||||
>
|
||||
<span v-if="!formLoading">同意授权</span>
|
||||
<span v-else>授 权 中...</span>
|
||||
</el-button>
|
||||
<el-button class="w-3/10" @click.prevent="handleAuthorize(false)">拒绝</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import * as OAuth2Api from '@/api/login/oauth2'
|
||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
defineOptions({ name: 'SSOLogin' })
|
||||
|
||||
const route = useRoute() // 路由
|
||||
const { currentRoute } = useRouter() // 路由
|
||||
const { getLoginState, setLoginState } = useLoginState()
|
||||
|
||||
const client = ref({
|
||||
// 客户端信息
|
||||
name: '',
|
||||
logo: ''
|
||||
})
|
||||
interface queryType {
|
||||
responseType: string
|
||||
clientId: string
|
||||
redirectUri: string
|
||||
state: string
|
||||
scopes: string[]
|
||||
}
|
||||
const queryParams = reactive<queryType>({
|
||||
// URL 上的 client_id、scope 等参数
|
||||
responseType: '',
|
||||
clientId: '',
|
||||
redirectUri: '',
|
||||
state: '',
|
||||
scopes: [] // 优先从 query 参数获取;如果未传递,从后端获取
|
||||
})
|
||||
const ssoVisible = computed(() => unref(getLoginState) === LoginStateEnum.SSO) // 是否展示 SSO 登录的表单
|
||||
interface formType {
|
||||
scopes: string[]
|
||||
}
|
||||
const formData = reactive<formType>({
|
||||
scopes: [] // 已选中的 scope 数组
|
||||
})
|
||||
const formLoading = ref(false) // 表单是否提交中
|
||||
|
||||
/** 初始化授权信息 */
|
||||
const init = async () => {
|
||||
// 防止在没有登录的情况下循环弹窗
|
||||
if (typeof route.query.client_id === 'undefined') return
|
||||
// 解析参数
|
||||
// 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write
|
||||
// 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
|
||||
queryParams.responseType = route.query.response_type as string
|
||||
queryParams.clientId = route.query.client_id as string
|
||||
queryParams.redirectUri = route.query.redirect_uri as string
|
||||
queryParams.state = route.query.state as string
|
||||
if (route.query.scope) {
|
||||
queryParams.scopes = (route.query.scope as string).split(' ')
|
||||
}
|
||||
|
||||
// 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
|
||||
if (queryParams.scopes.length > 0) {
|
||||
const data = await doAuthorize(true, queryParams.scopes, [])
|
||||
if (data) {
|
||||
location.href = data
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取授权页的基本信息
|
||||
const data = await OAuth2Api.getAuthorize(queryParams.clientId)
|
||||
client.value = data.client
|
||||
// 解析 scope
|
||||
let scopes
|
||||
// 1.1 如果 params.scope 非空,则过滤下返回的 scopes
|
||||
if (queryParams.scopes.length > 0) {
|
||||
scopes = []
|
||||
for (const scope of data.scopes) {
|
||||
if (queryParams.scopes.indexOf(scope.key) >= 0) {
|
||||
scopes.push(scope)
|
||||
}
|
||||
}
|
||||
// 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它
|
||||
} else {
|
||||
scopes = data.scopes
|
||||
for (const scope of scopes) {
|
||||
queryParams.scopes.push(scope.key)
|
||||
}
|
||||
}
|
||||
// 生成已选中的 checkedScopes
|
||||
for (const scope of scopes) {
|
||||
if (scope.value) {
|
||||
formData.scopes.push(scope.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理授权的提交 */
|
||||
const handleAuthorize = async (approved) => {
|
||||
// 计算 checkedScopes + uncheckedScopes
|
||||
let checkedScopes
|
||||
let uncheckedScopes
|
||||
if (approved) {
|
||||
// 同意授权,按照用户的选择
|
||||
checkedScopes = formData.scopes
|
||||
uncheckedScopes = queryParams.scopes.filter((item) => checkedScopes.indexOf(item) === -1)
|
||||
} else {
|
||||
// 拒绝,则都是取消
|
||||
checkedScopes = []
|
||||
uncheckedScopes = queryParams.scopes
|
||||
}
|
||||
// 提交授权的请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await doAuthorize(false, checkedScopes, uncheckedScopes)
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
location.href = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 调用授权 API 接口 */
|
||||
const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {
|
||||
return OAuth2Api.authorize(
|
||||
queryParams.responseType,
|
||||
queryParams.clientId,
|
||||
queryParams.redirectUri,
|
||||
queryParams.state,
|
||||
autoApprove,
|
||||
checkedScopes,
|
||||
uncheckedScopes
|
||||
)
|
||||
}
|
||||
|
||||
/** 格式化 scope 文本 */
|
||||
const formatScope = (scope) => {
|
||||
// 格式化 scope 授权范围,方便用户理解。
|
||||
// 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。
|
||||
switch (scope) {
|
||||
case 'user.read':
|
||||
return '访问你的个人信息'
|
||||
case 'user.write':
|
||||
return '修改你的个人信息'
|
||||
default:
|
||||
return scope
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听当前路由为 SSOLogin 时,进行数据的初始化 */
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
if (route.name === 'SSOLogin') {
|
||||
setLoginState(LoginStateEnum.SSO)
|
||||
init()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
7
src/views/Login/components/index.ts
Normal file
7
src/views/Login/components/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import LoginForm from './LoginForm.vue'
|
||||
import MobileForm from './MobileForm.vue'
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import QrCodeForm from './QrCodeForm.vue'
|
||||
import SSOLoginVue from './SSOLogin.vue'
|
||||
|
||||
export { LoginForm, MobileForm, LoginFormTitle, QrCodeForm, SSOLoginVue }
|
||||
42
src/views/Login/components/useLogin.ts
Normal file
42
src/views/Login/components/useLogin.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Ref } from 'vue'
|
||||
|
||||
export enum LoginStateEnum {
|
||||
LOGIN,
|
||||
REGISTER,
|
||||
RESET_PASSWORD,
|
||||
MOBILE,
|
||||
QR_CODE,
|
||||
SSO
|
||||
}
|
||||
|
||||
const currentState = ref(LoginStateEnum.LOGIN)
|
||||
|
||||
export function useLoginState() {
|
||||
function setLoginState(state: LoginStateEnum) {
|
||||
currentState.value = state
|
||||
}
|
||||
const getLoginState = computed(() => currentState.value)
|
||||
|
||||
function handleBackLogin() {
|
||||
setLoginState(LoginStateEnum.LOGIN)
|
||||
}
|
||||
|
||||
return {
|
||||
setLoginState,
|
||||
getLoginState,
|
||||
handleBackLogin
|
||||
}
|
||||
}
|
||||
|
||||
export function useFormValid<T extends Object = any>(formRef: Ref<any>) {
|
||||
async function validForm() {
|
||||
const form = unref(formRef)
|
||||
if (!form) return
|
||||
const data = await form.validate()
|
||||
return data as T
|
||||
}
|
||||
|
||||
return {
|
||||
validForm
|
||||
}
|
||||
}
|
||||
113
src/views/Login/sutra/Login.vue
Normal file
113
src/views/Login/sutra/Login.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div :class="prefixCls" class="relative w-100% h-100vh">
|
||||
<div
|
||||
class="content flex pos-relative left-0 top-50% z-2 w-100% h-31.25vw transform-translate-y--50%"
|
||||
>
|
||||
<div
|
||||
class="pos-absolute top-50% right-10% transform-translate-y--50% content-form bg-#fff inline-block border-rd-10px"
|
||||
>
|
||||
<div class="pos-relative w-300px p-34px">
|
||||
<div
|
||||
class="pos-absolute top-8px right-8px flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end"
|
||||
>
|
||||
<div class="flex items-center justify-end space-x-10px">
|
||||
<LocaleDropdown class="dark:text-white lt-xl:text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 账号登录 -->
|
||||
<LoginForm loginType="easy" />
|
||||
<!-- 手机登录 -->
|
||||
<MobileForm />
|
||||
<!-- 二维码登录 -->
|
||||
<QrCodeForm />
|
||||
<!-- 三方登录 -->
|
||||
<SSOLoginVue />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- transform-translate-y--330px -->
|
||||
<div
|
||||
class="w-100% pos-absolute left-0 w-100% h-auto z-10 transform-translate-y--50%"
|
||||
:style="{ top: bgTop }"
|
||||
>
|
||||
<el-divider>
|
||||
<div class="flex items-center">
|
||||
<el-image fit="cover" alt="" class="h-48px w-auto" :src="logoImage" />
|
||||
<span class="ml-10px text-20px">JeeLowCode 低代码开发平台</span>
|
||||
</div>
|
||||
</el-divider>
|
||||
</div>
|
||||
<div class="pos-absolute left-0 bottom-0 w-100% h-auto z-1">
|
||||
<el-image class="w-100% h-auto" :src="footerImage" fit="cover" />
|
||||
<div class="pos-absolute left-0 bottom-5px w-100% text-center">
|
||||
<a href="http://beian.miit.gov.cn" class="c-#0008 text-12px">粤ICP备xxx号</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from '../components'
|
||||
|
||||
import footerImage from '@/assets/imgs/login/login_sutra_2.png'
|
||||
import logoImage from '@/assets/imgs/logo_min.png'
|
||||
|
||||
defineOptions({ name: 'SutraLogin' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('sutra-login')
|
||||
const windowSize = useWindowSize()
|
||||
const bgTop = computed(() => {
|
||||
const bgHeight = windowSize.width.value / 3.2
|
||||
const topHeight = (windowSize.height.value - bgHeight) / 4 + 20
|
||||
if (topHeight < 30) return '30px'
|
||||
return topHeight + 'px'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$prefix-cls: #{$namespace}-sutra-login;
|
||||
|
||||
.#{$prefix-cls} {
|
||||
overflow: hidden;
|
||||
|
||||
.content {
|
||||
overflow: hidden;
|
||||
background-image: url('@/assets/imgs/login/login_sutra_1.png');
|
||||
background-size: cover;
|
||||
|
||||
.content-form {
|
||||
::v-deep(.login-form-default) {
|
||||
.el-divider {
|
||||
margin: 5px 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.login-form) {
|
||||
.form-title {
|
||||
margin: 0 !important;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.login-btn-col {
|
||||
.el-form-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.login-form-qrcode) {
|
||||
.form-title {
|
||||
margin-bottom: 15px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
src/views/Profile/Index.vue
Normal file
65
src/views/Profile/Index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<el-card class="user w-1/3" shadow="hover" >
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ t('profile.user.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<ProfileUser />
|
||||
</el-card>
|
||||
<el-card class="user ml-3 w-2/3" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ t('profile.info.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-tabs v-model="activeName" class="profile-tabs" style="height: 400px" tab-position="top">
|
||||
<el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo">
|
||||
<BasicInfo />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('profile.info.resetPwd')" name="resetPwd">
|
||||
<ResetPwd />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('profile.info.userSocial')" name="userSocial">
|
||||
<UserSocial v-model:activeName="activeName" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'
|
||||
|
||||
const { t } = useI18n()
|
||||
defineOptions({ name: 'Profile' })
|
||||
const activeName = ref('basicInfo')
|
||||
</script>
|
||||
<style scoped>
|
||||
.user {
|
||||
max-height: 960px;
|
||||
padding: 15px 20px 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.el-card .el-card__header, .el-card .el-card__body) {
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
.profile-tabs > .el-tabs__content {
|
||||
padding: 32px;
|
||||
font-weight: 600;
|
||||
color: #6b778c;
|
||||
}
|
||||
|
||||
.el-tabs--left .el-tabs__content {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
88
src/views/Profile/components/BasicInfo.vue
Normal file
88
src/views/Profile/components/BasicInfo.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="py-10px">
|
||||
<avue-form
|
||||
v-model="formData"
|
||||
:option="formOption"
|
||||
@submit="submit"
|
||||
@reset-change="init"
|
||||
v-loading="loading"
|
||||
>
|
||||
</avue-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getUserProfile, updateUserProfile } from '@/api/system/user/profile'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'BasicInfo' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const userStore = useUserStore()
|
||||
|
||||
const formData = ref<any>({})
|
||||
const formOption = ref({
|
||||
labelWidth: 120,
|
||||
span: 24,
|
||||
submitText: t('common.save'),
|
||||
emptyText: t('common.reset'),
|
||||
column: {
|
||||
nickname: {
|
||||
label: t('profile.user.nickname'),
|
||||
rules: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }]
|
||||
},
|
||||
mobile: {
|
||||
label: t('profile.user.mobile'),
|
||||
rules: [
|
||||
{ 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'
|
||||
}
|
||||
]
|
||||
},
|
||||
email: {
|
||||
label: t('profile.user.email'),
|
||||
rules: [
|
||||
{ required: true, message: t('profile.rules.mail'), trigger: 'blur' },
|
||||
{
|
||||
type: 'email',
|
||||
message: t('profile.rules.truemail'),
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
},
|
||||
sex: {
|
||||
label: t('profile.user.sex'),
|
||||
type: 'radio',
|
||||
dicData: [
|
||||
{ label: t('profile.user.man'), value: 1 },
|
||||
{ label: t('profile.user.woman'), value: 2 }
|
||||
],
|
||||
value: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
const loading = ref(false)
|
||||
|
||||
const submit = async (form, done) => {
|
||||
loading.value = true
|
||||
done()
|
||||
await updateUserProfile(form)
|
||||
message.success(t('common.updateSuccess'))
|
||||
const profile = await init()
|
||||
userStore.setUserNicknameAction(profile.nickname)
|
||||
userStore.setUserMobilAction(profile.mobile)
|
||||
}
|
||||
const init = async () => {
|
||||
loading.value = true
|
||||
const res = await getUserProfile()
|
||||
formData.value = res
|
||||
loading.value = false
|
||||
return res
|
||||
}
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
})
|
||||
</script>
|
||||
128
src/views/Profile/components/ProfileUser.vue
Normal file
128
src/views/Profile/components/ProfileUser.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<UserAvatar :img="userInfo?.avatar" />
|
||||
</div>
|
||||
<ul class="list-group list-group-striped">
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:user" />
|
||||
{{ t('profile.user.username') }}
|
||||
<div class="pull-right">{{ userInfo?.username }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:phone" />
|
||||
{{ t('profile.user.mobile') }}
|
||||
<div class="pull-right">{{ userInfo?.mobile }}</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="fontisto:email" />
|
||||
{{ t('profile.user.email') }}
|
||||
<div class="pull-right">{{ userInfo?.email }}</div>
|
||||
</li>
|
||||
<li class="list-group-item flex justify-between">
|
||||
<div class="flex-basis-100px flex-shrink-0">
|
||||
<Icon class="mr-5px" icon="carbon:tree-view-alt" />
|
||||
{{ t('profile.user.dept') }}
|
||||
</div>
|
||||
<div class="flex-1 flex flex-wrap justify-end gap-5px">
|
||||
<div v-for="dept in userInfo.deptInfoList" :key="dept.deptId">
|
||||
<el-popover placement="right" width="240" trigger="hover">
|
||||
<template #reference>
|
||||
<el-tag type="primary" class="cursor-pointer">{{ dept.deptName }}</el-tag>
|
||||
</template>
|
||||
<div class="flex flex-col gap-y-4px">
|
||||
<template v-for="type in keyList" :key="type.value">
|
||||
<div class="flex" v-if="dept[type.key]">
|
||||
<span class="flex-basis-42px flex-shrink-0 c-#909399">{{ type.label }}:</span>
|
||||
<span class="flex-1">{{ dept[type.key] }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item" v-if="userInfo.rankInfoList?.length">
|
||||
<Icon class="mr-5px" icon="ep:suitcase" />
|
||||
{{ t('profile.user.rank') }}
|
||||
<div v-if="userInfo?.rankInfoList" class="pull-right">
|
||||
{{ userInfo?.rankInfoList.map((rank) => rank.name).join(',') }}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="list-group-item">
|
||||
<Icon class="mr-5px" icon="ep:calendar" />
|
||||
{{ t('profile.user.createTime') }}
|
||||
<div class="pull-right">{{ formatDate(userInfo.createTime) }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import UserAvatar from './UserAvatar.vue'
|
||||
|
||||
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
||||
|
||||
defineOptions({ name: 'ProfileUser' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const userInfo = ref({} as ProfileVO)
|
||||
|
||||
const keyList = [
|
||||
{ label: '部门', value: 'deptName', key: 'deptName' },
|
||||
{ label: '角色', value: 'roleInfoList', key: 'role' },
|
||||
{ label: '职务', value: 'dutyInfoList', key: 'duty' },
|
||||
{ label: '职位', value: 'positionInfoList', key: 'position' },
|
||||
{ label: '岗位', value: 'postInfoList', key: 'post' }
|
||||
]
|
||||
|
||||
const getUserInfo = async () => {
|
||||
const users = await getUserProfile()
|
||||
users.deptInfoList = users.deptInfoList.map((dept) => {
|
||||
keyList.forEach(({ value, key }) => {
|
||||
if (value === 'deptName') return
|
||||
if (dept[value]?.length) dept[key] = dept[value].map((info) => info.name).join('、')
|
||||
else dept[key] = ''
|
||||
})
|
||||
return dept
|
||||
})
|
||||
userInfo.value = users
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getUserInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-center {
|
||||
position: relative;
|
||||
height: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list-group-striped > .list-group-item {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
padding: 11px 0;
|
||||
margin-bottom: -1px;
|
||||
font-size: 13px;
|
||||
border-top: 1px solid #e7eaec;
|
||||
border-bottom: 1px solid #e7eaec;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right !important;
|
||||
}
|
||||
</style>
|
||||
72
src/views/Profile/components/ResetPwd.vue
Normal file
72
src/views/Profile/components/ResetPwd.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="py-10px">
|
||||
<avue-form v-model="formData" :option="formOption" @submit="submit" v-loading="loading">
|
||||
<template #oldPassword>
|
||||
<InputPassword v-model="formData.oldPassword" />
|
||||
</template>
|
||||
<template #newPassword>
|
||||
<InputPassword v-model="formData.newPassword" strength />
|
||||
</template>
|
||||
<template #confirmPassword>
|
||||
<InputPassword v-model="formData.confirmPassword" strength />
|
||||
</template>
|
||||
</avue-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { InputPassword } from '@/components/InputPassword'
|
||||
import { updateUserPassword } from '@/api/system/user/profile'
|
||||
|
||||
defineOptions({ name: 'ResetPwd' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
|
||||
const equalToPassword = (_rule, value, callback) => {
|
||||
if (formData.value.newPassword !== value) {
|
||||
callback(new Error(t('profile.password.diffPwd')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const loading = ref(false)
|
||||
const formData = ref<any>({})
|
||||
const formOption = ref({
|
||||
labelWidth: 120,
|
||||
span: 24,
|
||||
submitText: t('common.save'),
|
||||
emptyText: t('common.reset'),
|
||||
column: {
|
||||
oldPassword: {
|
||||
label: t('profile.password.oldPassword'),
|
||||
rules: [
|
||||
{ required: true, message: t('profile.password.oldPwdMsg'), trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
newPassword: {
|
||||
label: t('profile.password.newPassword'),
|
||||
rules: [
|
||||
{ required: true, message: t('profile.password.newPwdMsg'), trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
confirmPassword: {
|
||||
label: t('profile.password.confirmPassword'),
|
||||
rules: [
|
||||
{ required: true, message: t('profile.password.cfPwdMsg'), trigger: 'blur' },
|
||||
{ required: true, validator: equalToPassword, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const submit = async (form, done) => {
|
||||
done()
|
||||
loading.value = true
|
||||
await updateUserPassword(form.oldPassword, form.newPassword).finally(
|
||||
() => (loading.value = false)
|
||||
)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
</script>
|
||||
45
src/views/Profile/components/UserAvatar.vue
Normal file
45
src/views/Profile/components/UserAvatar.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="change-avatar">
|
||||
<CropperAvatar
|
||||
ref="cropperRef"
|
||||
:btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }"
|
||||
:showBtn="false"
|
||||
:value="img"
|
||||
width="120px"
|
||||
@change="handelUpload"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { uploadAvatar } from '@/api/system/user/profile'
|
||||
import { CropperAvatar } from '@/components/Cropper'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
|
||||
defineOptions({ name: 'UserAvatar' })
|
||||
|
||||
defineProps({
|
||||
img: propTypes.string.def('')
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
|
||||
const cropperRef = ref()
|
||||
const handelUpload = async ({ file }) => {
|
||||
const res = await uploadAvatar({ updateSupport: 0, file })
|
||||
cropperRef.value.close()
|
||||
userStore.setUserAvatarAction(res.data.fileUrl)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.change-avatar {
|
||||
img {
|
||||
display: block;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
107
src/views/Profile/components/UserSocial.vue
Normal file
107
src/views/Profile/components/UserSocial.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<el-table :data="socialUsers" :show-header="false">
|
||||
<el-table-column fixed="left" title="序号" type="seq" width="60" />
|
||||
<el-table-column align="left" label="社交平台" width="120">
|
||||
<template #default="{ row }">
|
||||
<img :src="row.img" alt="" class="h-5 align-middle" />
|
||||
<p class="mr-5">{{ row.title }}</p>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.openid">
|
||||
已绑定
|
||||
<XTextButton class="mr-5" title="(解绑)" type="primary" @click="unbind(row)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
未绑定
|
||||
<XTextButton class="mr-5" title="(绑定)" type="primary" @click="bind(row)" />
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { SystemUserSocialTypeEnum } from '@/utils/constants'
|
||||
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
||||
import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'
|
||||
|
||||
defineOptions({ name: 'UserSocial' })
|
||||
defineProps<{
|
||||
activeName: string
|
||||
}>()
|
||||
const message = useMessage()
|
||||
const socialUsers = ref<any[]>([])
|
||||
const userInfo = ref<ProfileVO>()
|
||||
|
||||
const initSocial = async () => {
|
||||
socialUsers.value = [] // 重置避免无限增长
|
||||
const res = await getUserProfile()
|
||||
userInfo.value = res
|
||||
for (const i in SystemUserSocialTypeEnum) {
|
||||
const socialUser = { ...SystemUserSocialTypeEnum[i] }
|
||||
socialUsers.value.push(socialUser)
|
||||
if (userInfo.value?.socialUsers) {
|
||||
for (const j in userInfo.value.socialUsers) {
|
||||
if (socialUser.type === userInfo.value.socialUsers[j].type) {
|
||||
socialUser.openid = userInfo.value.socialUsers[j].openid
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const route = useRoute()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:activeName', v: string): void
|
||||
}>()
|
||||
const bindSocial = () => {
|
||||
// 社交绑定
|
||||
const type = getUrlValue('type')
|
||||
const code = route.query.code
|
||||
const state = route.query.state
|
||||
if (!code) {
|
||||
return
|
||||
}
|
||||
socialBind(type, code, state).then(() => {
|
||||
message.success('绑定成功')
|
||||
emit('update:activeName', 'userSocial')
|
||||
})
|
||||
}
|
||||
|
||||
// 双层 encode 需要在回调后进行 decode
|
||||
function getUrlValue(key: string): string {
|
||||
const url = new URL(decodeURIComponent(location.href))
|
||||
return url.searchParams.get(key) ?? ''
|
||||
}
|
||||
|
||||
const bind = (row) => {
|
||||
// 双层 encode 解决钉钉回调 type 参数丢失的问题
|
||||
const redirectUri = location.origin + '/user/profile?' + encodeURIComponent(`type=${row.type}`)
|
||||
// 进行跳转
|
||||
socialAuthRedirect(row.type, encodeURIComponent(redirectUri)).then((res) => {
|
||||
window.location.href = res
|
||||
})
|
||||
}
|
||||
const unbind = async (row) => {
|
||||
const res = await socialUnbind(row.type, row.openid)
|
||||
if (res) {
|
||||
row.openid = undefined
|
||||
}
|
||||
message.success('解绑成功')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await initSocial()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
() => {
|
||||
bindSocial()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
7
src/views/Profile/components/index.ts
Normal file
7
src/views/Profile/components/index.ts
Normal file
@@ -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 }
|
||||
28
src/views/Redirect/Redirect.vue
Normal file
28
src/views/Redirect/Redirect.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'Redirect' })
|
||||
|
||||
const { currentRoute, replace } = useRouter()
|
||||
const { params, query } = unref(currentRoute)
|
||||
const { path, _redirect_type = 'path' } = params
|
||||
|
||||
Reflect.deleteProperty(params, '_redirect_type')
|
||||
Reflect.deleteProperty(params, 'path')
|
||||
|
||||
const _path = Array.isArray(path) ? path.join('/') : path
|
||||
|
||||
if (_redirect_type === 'name') {
|
||||
replace({
|
||||
name: _path,
|
||||
query,
|
||||
params
|
||||
})
|
||||
} else {
|
||||
replace({
|
||||
path: _path.startsWith('/') ? _path : '/' + _path,
|
||||
query
|
||||
})
|
||||
}
|
||||
</script>
|
||||
165
src/views/bpm/definition/index.vue
Normal file
165
src/views/bpm/definition/index.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model:page="tablePage"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
@click="handleAssignRule(row)"
|
||||
v-hasPermi="['bpm:task-assign-rule:query']"
|
||||
>
|
||||
分配规则
|
||||
</el-button>
|
||||
</template>
|
||||
<template #name="{ row }">
|
||||
<el-button type="primary" link @click="handleBpmnDetail(row)">
|
||||
<span>{{ row.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
<template #category="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="row.category || ''" />
|
||||
</template>
|
||||
<template #version="{ row }">
|
||||
<el-tag>v{{ row.version }}</el-tag>
|
||||
</template>
|
||||
<template #suspensionState="{ row }">
|
||||
<el-tag type="success" v-if="row.suspensionState === 1">激活</el-tag>
|
||||
<el-tag type="warning" v-if="row.suspensionState === 2">挂起</el-tag>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 弹窗:流程模型图的预览 -->
|
||||
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
|
||||
<MyProcessViewer
|
||||
key="designer"
|
||||
v-model="bpmnXML"
|
||||
:value="bpmnXML || ''"
|
||||
v-bind="bpmnControlForm"
|
||||
:prefix="bpmnControlForm.prefix"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
|
||||
defineOptions({ name: 'ModelVersions' })
|
||||
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
const { query } = useRoute() // 查询参数
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
addBtn: false,
|
||||
menuWidth: 140,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
calcHeight: 20,
|
||||
column: [
|
||||
{ prop: 'id', label: '定义编号', width: 370 },
|
||||
{ prop: 'name', label: '流程名称' },
|
||||
{
|
||||
prop: 'category',
|
||||
label: '定义分类',
|
||||
width: 100,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)
|
||||
},
|
||||
{ prop: 'version', label: '流程版本', width: 100, bind: 'processDefinition.version' },
|
||||
{ prop: 'suspensionState', label: '状态', width: 80 },
|
||||
{
|
||||
prop: 'deploymentTime',
|
||||
label: '部署时间',
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'description',
|
||||
label: '定义描述',
|
||||
type: 'textarea'
|
||||
}
|
||||
]
|
||||
}) //表格配置
|
||||
const tableData = ref([])
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const permission = getCurrPermi(['bpm:model'])
|
||||
const crudRef = ref()
|
||||
|
||||
/** 流程图的详情按钮操作 */
|
||||
const bpmnDetailVisible = ref(false)
|
||||
const bpmnXML = ref(null)
|
||||
const bpmnControlForm = ref({
|
||||
prefix: 'flowable'
|
||||
})
|
||||
|
||||
/** 点击任务分配按钮 */
|
||||
const handleAssignRule = (row) => {
|
||||
push({
|
||||
name: 'BpmTaskAssignRuleList',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleBpmnDetail = async (row) => {
|
||||
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id)
|
||||
bpmnDetailVisible.value = true
|
||||
}
|
||||
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
const searchObj = {
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize,
|
||||
key: query.key
|
||||
}
|
||||
const data = await DefinitionApi.getProcessDefinitionPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
onMounted(() => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData()
|
||||
})
|
||||
</script>
|
||||
221
src/views/bpm/group/index.vue
Normal file
221
src/views/bpm/group/index.vue
Normal file
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemDictType' })
|
||||
|
||||
interface DictType {
|
||||
label: string
|
||||
value: number
|
||||
}
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
id: {
|
||||
label: '编号',
|
||||
display: false
|
||||
},
|
||||
name: {
|
||||
label: '组名',
|
||||
search: true,
|
||||
rules: [{ required: true, message: '组名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
description: {
|
||||
label: '描述',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4
|
||||
},
|
||||
memberUserIds: {
|
||||
label: '成员',
|
||||
type: 'select',
|
||||
span: 12,
|
||||
multiple: true,
|
||||
dicData: [] as DictType[],
|
||||
rules: [{ required: true, message: '成员不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'radio',
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['bpm:user-group'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await UserGroupApi.getUserGroupPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await UserGroupApi.getUserGroup(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await UserGroupApi.createUserGroup(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await UserGroupApi.updateUserGroup(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await UserGroupApi.deleteUserGroup(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
// 加载用户列表
|
||||
const data = await UserApi.getSimpleUserList()
|
||||
|
||||
tableOption.column.memberUserIds.dicData = data.map((item) => {
|
||||
return {
|
||||
label: item.nickname,
|
||||
value: item.id
|
||||
}
|
||||
})
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
140
src/views/bpm/model/ModelImportForm.vue
Normal file
140
src/views/bpm/model/ModelImportForm.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="导入流程" width="400">
|
||||
<div>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:action="importUrl"
|
||||
:auto-upload="false"
|
||||
:data="formData"
|
||||
:disabled="formLoading"
|
||||
:headers="uploadHeaders"
|
||||
:limit="1"
|
||||
:on-error="submitFormError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitFormSuccess"
|
||||
accept=".bpmn, .xml"
|
||||
drag
|
||||
name="bpmnFile"
|
||||
>
|
||||
<Icon class="el-icon--upload" icon="ep:upload-filled" />
|
||||
<div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip" style="color: red">
|
||||
提示:仅允许导入“bpm”或“xml”格式文件!
|
||||
</div>
|
||||
<div>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
|
||||
<el-form-item label="流程标识" prop="key">
|
||||
<el-input
|
||||
v-model="formData.key"
|
||||
placeholder="请输入流标标识"
|
||||
style="width: 250px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input v-model="formData.name" clearable placeholder="请输入流程名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程描述" prop="description">
|
||||
<el-input v-model="formData.description" clearable type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
|
||||
defineOptions({ name: 'ModelImportForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
key: '',
|
||||
name: '',
|
||||
description: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const uploadRef = ref() // 上传 Ref
|
||||
const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import'
|
||||
const uploadHeaders = ref() // 上传 Header 头
|
||||
const fileList = ref([]) // 文件列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
uploadHeaders.value = {
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
}
|
||||
formLoading.value = true
|
||||
uploadRef.value!.submit()
|
||||
}
|
||||
|
||||
/** 文件上传成功 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitFormSuccess = async (response: any) => {
|
||||
if (response.code !== 0) {
|
||||
message.error(response.msg)
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
// 提示成功
|
||||
message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】')
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('导入流程失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
// 重置表单
|
||||
formData.value = {
|
||||
key: '',
|
||||
name: '',
|
||||
description: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
</script>
|
||||
105
src/views/bpm/model/editor/index.vue
Normal file
105
src/views/bpm/model/editor/index.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 流程设计器,负责绘制流程等 -->
|
||||
<MyProcessDesigner
|
||||
key="designer"
|
||||
v-if="xmlString !== undefined"
|
||||
v-model="xmlString"
|
||||
:value="xmlString"
|
||||
v-bind="controlForm"
|
||||
keyboard
|
||||
ref="processDesigner"
|
||||
@init-finished="initModeler"
|
||||
:additionalModel="controlForm.additionalModel"
|
||||
@save="save"
|
||||
/>
|
||||
<!-- 流程属性器,负责编辑每个流程节点的属性 -->
|
||||
<MyProcessPenal
|
||||
key="penal"
|
||||
:bpmnModeler="modeler as any"
|
||||
:prefix="controlForm.prefix"
|
||||
class="process-panel"
|
||||
:model="model"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { MyProcessDesigner, MyProcessPenal } from '@/components/bpmnProcessDesigner/package'
|
||||
// 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务)
|
||||
import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
|
||||
// 自定义左侧菜单(修改 默认任务 为 用户任务)
|
||||
import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
|
||||
defineOptions({ name: 'BpmModelEditor' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const { query } = useRoute() // 路由的查询
|
||||
const message = useMessage() // 国际化
|
||||
|
||||
const xmlString = ref(undefined) // BPMN XML
|
||||
const modeler = ref(null) // BPMN Modeler
|
||||
const controlForm = ref({
|
||||
simulation: true,
|
||||
labelEditing: false,
|
||||
labelVisible: false,
|
||||
prefix: 'flowable',
|
||||
headerButtonSize: 'mini',
|
||||
additionalModel: [CustomContentPadProvider, CustomPaletteProvider]
|
||||
})
|
||||
const model = ref<ModelApi.ModelVO>() // 流程模型的信息
|
||||
|
||||
/** 初始化 modeler */
|
||||
const initModeler = (item) => {
|
||||
setTimeout(() => {
|
||||
modeler.value = item
|
||||
}, 10)
|
||||
}
|
||||
|
||||
/** 添加/修改模型 */
|
||||
const save = async (bpmnXml) => {
|
||||
const data = {
|
||||
...model.value,
|
||||
bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得
|
||||
} as unknown as ModelApi.ModelVO
|
||||
// 提交
|
||||
if (data.id) {
|
||||
await ModelApi.updateModel(data)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await ModelApi.createModel(data)
|
||||
message.success('新增成功')
|
||||
}
|
||||
// 跳转回去
|
||||
close()
|
||||
}
|
||||
|
||||
/** 关闭按钮 */
|
||||
const close = () => {
|
||||
router.push({ path: '/bpm/manager/model' })
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
const modelId = query.modelId as unknown as number
|
||||
if (!modelId) {
|
||||
message.error('缺少模型 modelId 编号')
|
||||
return
|
||||
}
|
||||
// 查询模型
|
||||
const data = await ModelApi.getModel(modelId)
|
||||
xmlString.value = data.bpmnXml
|
||||
model.value = {
|
||||
...data,
|
||||
bpmnXml: undefined // 清空 bpmnXml 属性
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.process-panel__container {
|
||||
position: absolute;
|
||||
top: 90px;
|
||||
right: 60px;
|
||||
}
|
||||
</style>
|
||||
523
src/views/bpm/model/index.vue
Normal file
523
src/views/bpm/model/index.vue
Normal file
@@ -0,0 +1,523 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-del="rowDel"
|
||||
@row-update="rowUpdate"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button type="success" plain @click="openImportForm" v-hasPermi="['bpm:model:import']">
|
||||
<Icon icon="ep:upload" class="mr-5px" /> 导入流程
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="crudRef.rowEdit(scope.row, scope.index)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
>
|
||||
修改流程
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDesign(scope.row)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
>
|
||||
设计流程
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleAssignRule(scope.row)"
|
||||
v-hasPermi="['bpm:task-assign-rule:query']"
|
||||
>
|
||||
分配规则
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDeploy(scope.row)"
|
||||
v-hasPermi="['bpm:model:deploy']"
|
||||
>
|
||||
发布流程
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['bpm:process-definition:query']"
|
||||
@click="handleDefinitionList(scope.row)"
|
||||
>
|
||||
流程定义
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="crudRef.rowDel(scope.row, scope.index)"
|
||||
v-hasPermi="['bpm:model:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #name="scope">
|
||||
<el-button type="primary" link @click="handleBpmnDetail(scope.row)">
|
||||
<span>{{ scope.row.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
<template #category="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.category"
|
||||
:type="DICT_TYPE.BPM_MODEL_CATEGORY"
|
||||
:value="scope.row.category"
|
||||
/>
|
||||
</template>
|
||||
<template #formType="{ row }">
|
||||
<el-button v-if="row.formType === 10" type="primary" link @click="handleFormDetail(row)">
|
||||
<span>{{ row.formName }}</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="row.formType === 20"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(row)"
|
||||
>
|
||||
<span>{{ row.formCustomCreatePath }}</span>
|
||||
</el-button>
|
||||
<label v-else>暂无表单</label>
|
||||
</template>
|
||||
<template #processDefinitionVersion="scope">
|
||||
<el-tag v-if="scope.row.processDefinition">
|
||||
v{{ scope.row.processDefinition.version }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="warning">未部署</el-tag>
|
||||
</template>
|
||||
<template #processDefinitionSuspensionState="scope">
|
||||
<el-switch
|
||||
v-if="scope.row.processDefinition"
|
||||
v-model="scope.row.processDefinition.suspensionState"
|
||||
:active-value="1"
|
||||
:inactive-value="2"
|
||||
@change="handleChangeState(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
<template #deploymentTime="scope">
|
||||
<span v-if="scope.row.processDefinition">
|
||||
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
|
||||
</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗:流程模型图的预览 -->
|
||||
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
|
||||
<MyProcessViewer
|
||||
key="designer"
|
||||
v-model="bpmnXML"
|
||||
:value="bpmnXML || ''"
|
||||
v-bind="bpmnControlForm"
|
||||
:prefix="bpmnControlForm.prefix"
|
||||
/>
|
||||
</Dialog>
|
||||
<!-- 表单弹窗:导入流程 -->
|
||||
<ModelImportForm ref="importFormRef" @success="getTableData" />
|
||||
<!-- 表单预览 -->
|
||||
<FormView
|
||||
v-model="showFormView"
|
||||
formType="add"
|
||||
showType="dialog"
|
||||
:showButton="false"
|
||||
v-bind="formOption"
|
||||
></FormView>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import ModelImportForm from '@/views/bpm/model/ModelImportForm.vue'
|
||||
|
||||
defineOptions({ name: 'SystemTenant' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter() // 路由
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtnText: '新增流程',
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
menuWidth: 300,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: [
|
||||
{
|
||||
prop: 'key',
|
||||
label: '流程标识',
|
||||
search: true,
|
||||
rules: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||
editDisabled: true
|
||||
},
|
||||
{
|
||||
prop: 'name',
|
||||
label: '流程名称',
|
||||
search: true,
|
||||
rules: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||
editDisabled: true
|
||||
},
|
||||
{
|
||||
prop: 'category',
|
||||
label: '流程分类',
|
||||
width: 100,
|
||||
addDisplay: false,
|
||||
editDisplay: true,
|
||||
search: true,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY).map(el=>{return{...el, value: el.value + ''}}),
|
||||
rules: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }]
|
||||
},
|
||||
{
|
||||
prop: 'description',
|
||||
label: '流程描述',
|
||||
type: 'textarea',
|
||||
hide: true,
|
||||
minRows: 2,
|
||||
maxRows: 4
|
||||
},
|
||||
{
|
||||
prop: 'formType',
|
||||
label: '表单类型',
|
||||
type: 'radio',
|
||||
dicData: getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE),
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: true,
|
||||
control: (val) => {
|
||||
return {
|
||||
formId: { display: val == 10 },
|
||||
formCustomCreatePath: { display: val == 20 },
|
||||
formCustomViewPath: { display: val == 20 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'formId',
|
||||
label: '流程表单',
|
||||
type: 'select',
|
||||
hide: true,
|
||||
filterable: true,
|
||||
display: false,
|
||||
dicUrl: '/jeelowcode/desform/page',
|
||||
dicMethod: 'post',
|
||||
props: { label: 'desformName', value: 'id' },
|
||||
dicFormatter: (data) => data.records
|
||||
},
|
||||
{
|
||||
prop: 'formCustomCreatePath',
|
||||
label: '表单提交路由',
|
||||
hide: true,
|
||||
display: false,
|
||||
labelTip: '自定义表单的提交路径,使用 Vue 的路由地址'
|
||||
},
|
||||
{
|
||||
prop: 'formCustomViewPath',
|
||||
label: '表单查看地址',
|
||||
hide: true,
|
||||
display: false,
|
||||
labelTip: '自定义表单的查看组件地址,使用 Vue 的组件地址'
|
||||
},
|
||||
{
|
||||
prop: 'formType',
|
||||
label: '表单信息',
|
||||
display: false
|
||||
},
|
||||
{
|
||||
prop: 'createTime',
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '最新部署的流程定义',
|
||||
children: [
|
||||
{
|
||||
prop: 'processDefinitionVersion',
|
||||
label: '流程版本',
|
||||
width: 100,
|
||||
display: false
|
||||
},
|
||||
{
|
||||
prop: 'processDefinitionSuspensionState',
|
||||
label: '激活状态',
|
||||
width: 100,
|
||||
display: false
|
||||
},
|
||||
{
|
||||
prop: 'deploymentTime',
|
||||
label: '部署时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const showFormView = ref(false)
|
||||
const formOption = ref({
|
||||
formId: '',
|
||||
popOption: {
|
||||
title: ''
|
||||
}
|
||||
})
|
||||
|
||||
const permission = getCurrPermi(['bpm:model'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await ModelApi.getModelPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await ModelApi.getModel(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let params = {
|
||||
name: form.name,
|
||||
key: form.key,
|
||||
description: form.description,
|
||||
formCustomViewPath: '',
|
||||
formCustomCreatePath: '',
|
||||
formId: '',
|
||||
formType: 10
|
||||
}
|
||||
|
||||
let bool = await ModelApi.createModel(params).catch(() => false)
|
||||
if (bool) {
|
||||
// 提示,引导用户做后续的操作
|
||||
await ElMessageBox.alert(
|
||||
'<strong>新建模型成功!</strong>后续需要执行如下 4 个步骤:' +
|
||||
'<div>1. 点击【修改流程】按钮,配置流程的分类、表单信息</div>' +
|
||||
'<div>2. 点击【设计流程】按钮,绘制流程图</div>' +
|
||||
'<div>3. 点击【分配规则】按钮,设置每个用户任务的审批人</div>' +
|
||||
'<div>4. 点击【发布流程】按钮,完成流程的最终发布</div>' +
|
||||
'另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
|
||||
'重要提示',
|
||||
{
|
||||
dangerouslyUseHTMLString: true,
|
||||
type: 'success'
|
||||
}
|
||||
)
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await ModelApi.updateModel(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ModelApi.deleteModel(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 更新状态操作 */
|
||||
const handleChangeState = async (row) => {
|
||||
const state = row.processDefinition.suspensionState
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const id = row.id
|
||||
const statusState = state === 1 ? '激活' : '挂起'
|
||||
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
||||
await message.confirm(content)
|
||||
// 发起修改状态
|
||||
await ModelApi.updateModelState(id, state)
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {
|
||||
// 取消后,进行恢复按钮
|
||||
row.processDefinition.suspensionState = state === 1 ? 2 : 1
|
||||
}
|
||||
}
|
||||
|
||||
/** 流程图的详情按钮操作 */
|
||||
const bpmnDetailVisible = ref(false)
|
||||
const bpmnXML = ref(null)
|
||||
const bpmnControlForm = ref({
|
||||
prefix: 'flowable'
|
||||
})
|
||||
const handleBpmnDetail = async (row) => {
|
||||
const data = await ModelApi.getModel(row.id)
|
||||
bpmnXML.value = data.bpmnXml || ''
|
||||
bpmnDetailVisible.value = true
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const handleFormDetail = async (row) => {
|
||||
if (row.formType == 10) {
|
||||
formOption.value.formId = row.formId
|
||||
formOption.value.popOption.title = row.formName
|
||||
showFormView.value = true
|
||||
} else {
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const importFormRef = ref()
|
||||
const openImportForm = () => {
|
||||
importFormRef.value.open()
|
||||
}
|
||||
|
||||
/** 设计流程 */
|
||||
const handleDesign = (row) => {
|
||||
push({
|
||||
name: 'BpmModelEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 点击任务分配按钮 */
|
||||
const handleAssignRule = (row) => {
|
||||
push({
|
||||
name: 'BpmTaskAssignRuleList',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 发布流程 */
|
||||
const handleDeploy = async (row) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.confirm('是否部署该流程!!')
|
||||
// 发起部署
|
||||
await ModelApi.deployModel(row.id)
|
||||
message.success(t('部署成功'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 跳转到指定流程定义列表 */
|
||||
const handleDefinitionList = (row) => {
|
||||
push({
|
||||
name: 'BpmProcessDefinition',
|
||||
query: {
|
||||
key: row.key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
164
src/views/bpm/processInstance/create/index.vue
Normal file
164
src/views/bpm/processInstance/create/index.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<!-- 第一步,通过流程定义的列表,选择对应的流程 -->
|
||||
<ContentWrap v-if="!selectProcessInstance">
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
@refresh-change="getTableData"
|
||||
>
|
||||
<template #menu="{ row }">
|
||||
<el-button link type="primary" @click="handleSelect(row)" v-hasPermi="['bpm:model:update']">
|
||||
选择
|
||||
</el-button>
|
||||
</template>
|
||||
<template #category="{ row }">
|
||||
<el-tag>{{ row['$category'] }}</el-tag>
|
||||
</template>
|
||||
<template #version="{ row }">
|
||||
<el-tag>v{{ row.version }}</el-tag>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<!-- 第二步,填写表单,进行流程的提交 -->
|
||||
<ContentWrap v-else>
|
||||
<el-card class="box-card">
|
||||
<div class="clearfix">
|
||||
<span class="el-icon-document">申请信息【{{ selectProcessInstance.name }}】</span>
|
||||
<el-button style="float: right" type="primary" @click="selectProcessInstance = undefined">
|
||||
<Icon icon="ep:delete" /> 选择其它流程
|
||||
</el-button>
|
||||
</div>
|
||||
<el-col :span="24" style="margin-top: 20px">
|
||||
<FormView
|
||||
:form-id="formId"
|
||||
formType="add"
|
||||
showType="view"
|
||||
:beforeClose="submitForm"
|
||||
></FormView>
|
||||
</el-col>
|
||||
</el-card>
|
||||
<!-- 流程图预览 -->
|
||||
<ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML || ''" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceCreate' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const router = useRouter() // 路由
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
header: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
menuWidth: 300,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: [
|
||||
{
|
||||
prop: 'name',
|
||||
label: '流程名称',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'category',
|
||||
label: '流程分类',
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY),
|
||||
minWidth: 90
|
||||
},
|
||||
{
|
||||
prop: 'version',
|
||||
label: '流程版本',
|
||||
minWidth: 90
|
||||
},
|
||||
{
|
||||
prop: 'description',
|
||||
label: '流程描述',
|
||||
type: 'textarea',
|
||||
minWidth: 120
|
||||
}
|
||||
]
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const selectProcessInstance = ref() // 选择的流程实例
|
||||
const bpmnXML = ref(null)
|
||||
const formId = ref('')
|
||||
|
||||
const permission = getCurrPermi(['bpm:model'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
tableData.value = await DefinitionApi.getProcessDefinitionList({ suspensionState: 1 })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理选择流程的按钮操作 **/
|
||||
const handleSelect = async (row) => {
|
||||
// 设置选择的流程
|
||||
selectProcessInstance.value = row
|
||||
// 情况一:流程表单
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
formId.value = row.formId
|
||||
// 加载流程图
|
||||
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id)
|
||||
// 情况二:业务表单
|
||||
} else if (row.formCustomCreatePath) {
|
||||
await router.push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
// 这里暂时无需加载流程图,因为跳出到另外个 Tab;
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async (type, done, formData, loading) => {
|
||||
if (type == 'submit') {
|
||||
try {
|
||||
await ProcessInstanceApi.createProcessInstance({
|
||||
processDefinitionId: selectProcessInstance.value.id,
|
||||
variables: formData
|
||||
})
|
||||
// 提示
|
||||
message.success('发起流程成功')
|
||||
router.go(-1)
|
||||
} finally {
|
||||
done()
|
||||
}
|
||||
} else {
|
||||
if (loading) loading()
|
||||
else done()
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<el-card v-loading="loading" class="box-card">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">流程图</span>
|
||||
</template>
|
||||
<MyProcessViewer
|
||||
key="designer"
|
||||
:activityData="activityList"
|
||||
:prefix="bpmnControlForm.prefix"
|
||||
:processInstanceData="processInstance"
|
||||
:taskData="tasks"
|
||||
:value="bpmnXml"
|
||||
v-bind="bpmnControlForm"
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
import * as ActivityApi from '@/api/bpm/activity'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceBpmnViewer' })
|
||||
|
||||
const props = defineProps({
|
||||
loading: propTypes.bool, // 是否加载中
|
||||
id: propTypes.string, // 流程实例的编号
|
||||
processInstance: propTypes.any, // 流程实例的信息
|
||||
tasks: propTypes.array, // 流程任务的数组
|
||||
bpmnXml: propTypes.string // BPMN XML
|
||||
})
|
||||
|
||||
const bpmnControlForm = ref({
|
||||
prefix: 'flowable'
|
||||
})
|
||||
const activityList = ref([]) // 任务列表
|
||||
// const bpmnXML = computed(() => { // 不晓得为啊哈不能这么搞
|
||||
// if (!props.processInstance || !props.processInstance.processDefinition) {
|
||||
// return
|
||||
// }
|
||||
// return DefinitionApi.getProcessDefinitionBpmnXML(props.processInstance.processDefinition.id)
|
||||
// })
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
if (props.id) {
|
||||
activityList.value = await ActivityApi.getActivityList({
|
||||
processInstanceId: props.id
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
.box-card {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" title="子任务" size="70%">
|
||||
<!-- 当前任务 -->
|
||||
<template #header>
|
||||
<h4>【{{ baseTask.name }} 】审批人:{{ baseTask.assigneeUser?.nickname }}</h4>
|
||||
<el-button
|
||||
style="margin-left: 5px"
|
||||
v-if="isSubSignButtonVisible(baseTask)"
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleSubSign(baseTask)"
|
||||
>
|
||||
<Icon icon="ep:remove" /> 减签
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 子任务列表 -->
|
||||
<el-table :data="baseTask.children" style="width: 100%" row-key="id" border>
|
||||
<el-table-column prop="assigneeUser.nickname" label="审批人" />
|
||||
<el-table-column prop="assigneeUser.deptName" label="所在部门" />
|
||||
<el-table-column label="审批状态" prop="result">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="提交时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" prop="operation">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="isSubSignButtonVisible(scope.row)"
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleSubSign(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:remove" /> 减签
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 减签 -->
|
||||
<TaskSubSignDialogForm ref="taskSubSignDialogForm" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import TaskSubSignDialogForm from './TaskSubSignDialogForm.vue'
|
||||
|
||||
defineOptions({ name: 'ProcessInstanceChildrenTaskList' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const drawerVisible = ref(false) // 抽屉的是否展示
|
||||
|
||||
const baseTask = ref<object>({})
|
||||
/** 打开弹窗 */
|
||||
const open = async (task: any) => {
|
||||
if (isEmpty(task.children)) {
|
||||
message.warning('该任务没有子任务')
|
||||
return
|
||||
}
|
||||
baseTask.value = task
|
||||
// 展开抽屉
|
||||
drawerVisible.value = true
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 发起减签 */
|
||||
const taskSubSignDialogForm = ref()
|
||||
const handleSubSign = (item) => {
|
||||
taskSubSignDialogForm.value.open(item.id)
|
||||
}
|
||||
|
||||
/** 是否显示减签按钮 */
|
||||
const isSubSignButtonVisible = (task: any) => {
|
||||
if (task && task.children && !isEmpty(task.children)) {
|
||||
// 有子任务,且子任务有任意一个是 待处理 和 待前置任务完成 则显示减签按钮
|
||||
const subTask = task.children.find((item) => item.result === 1 || item.result === 9)
|
||||
return !isEmpty(subTask)
|
||||
}
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
128
src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue
Normal file
128
src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<el-card v-loading="loading" class="box-card">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">审批记录</span>
|
||||
</template>
|
||||
<el-col :span="24">
|
||||
<div class="block">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(item, index) in tasks"
|
||||
:key="index"
|
||||
:icon="getTimelineItemIcon(item)"
|
||||
:type="getTimelineItemType(item)"
|
||||
>
|
||||
<p style="font-weight: 700">
|
||||
任务:{{ item.name }}
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="item.result" />
|
||||
<el-button
|
||||
style="margin-left: 5px"
|
||||
v-if="!isEmpty(item.children)"
|
||||
@click="openChildrenTask(item)"
|
||||
>
|
||||
<Icon icon="ep:memo" />
|
||||
子任务
|
||||
</el-button>
|
||||
</p>
|
||||
<el-card :body-style="{ padding: '10px' }">
|
||||
<label v-if="item.assigneeUser" style="margin-right: 30px; font-weight: normal">
|
||||
审批人:{{ item.assigneeUser.nickname }}
|
||||
<el-tag size="small" type="info">{{ item.assigneeUser.deptName }}</el-tag>
|
||||
</label>
|
||||
<label v-if="item.createTime" style="font-weight: normal">创建时间:</label>
|
||||
<label style="font-weight: normal; color: #8a909c">
|
||||
{{ formatDate(item?.createTime) }}
|
||||
</label>
|
||||
<label v-if="item.endTime" style="margin-left: 30px; font-weight: normal">
|
||||
审批时间:
|
||||
</label>
|
||||
<label v-if="item.endTime" style="font-weight: normal; color: #8a909c">
|
||||
{{ formatDate(item?.endTime) }}
|
||||
</label>
|
||||
<label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal">
|
||||
耗时:
|
||||
</label>
|
||||
<label v-if="item.durationInMillis" style="font-weight: normal; color: #8a909c">
|
||||
{{ formatPast2(item?.durationInMillis) }}
|
||||
</label>
|
||||
<p v-if="item.reason">
|
||||
<el-tag :type="getTimelineItemType(item)">{{ item.reason }}</el-tag>
|
||||
</p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</el-col>
|
||||
<!-- 子任务 -->
|
||||
<ProcessInstanceChildrenTaskList ref="processInstanceChildrenTaskList" />
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate, formatPast2 } from '@/utils/formatTime'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import ProcessInstanceChildrenTaskList from './ProcessInstanceChildrenTaskList.vue'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceTaskList' })
|
||||
|
||||
defineProps({
|
||||
loading: propTypes.bool, // 是否加载中
|
||||
tasks: propTypes.arrayOf(propTypes.object) // 流程任务的数组
|
||||
})
|
||||
|
||||
/** 获得任务对应的 icon */
|
||||
const getTimelineItemIcon = (item) => {
|
||||
if (item.result === 1) {
|
||||
return 'el-icon-time'
|
||||
}
|
||||
if (item.result === 2) {
|
||||
return 'el-icon-check'
|
||||
}
|
||||
if (item.result === 3) {
|
||||
return 'el-icon-close'
|
||||
}
|
||||
if (item.result === 4) {
|
||||
return 'el-icon-remove-outline'
|
||||
}
|
||||
if (item.result === 5) {
|
||||
return 'el-icon-back'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 获得任务对应的颜色 */
|
||||
const getTimelineItemType = (item) => {
|
||||
if (item.result === 1) {
|
||||
return 'primary'
|
||||
}
|
||||
if (item.result === 2) {
|
||||
return 'success'
|
||||
}
|
||||
if (item.result === 3) {
|
||||
return 'danger'
|
||||
}
|
||||
if (item.result === 4) {
|
||||
return 'info'
|
||||
}
|
||||
if (item.result === 5) {
|
||||
return 'warning'
|
||||
}
|
||||
if (item.result === 6) {
|
||||
return 'default'
|
||||
}
|
||||
if (item.result === 7 || item.result === 8) {
|
||||
return 'warning'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 子任务
|
||||
*/
|
||||
const processInstanceChildrenTaskList = ref()
|
||||
|
||||
const openChildrenTask = (item) => {
|
||||
processInstanceChildrenTaskList.value.open(item)
|
||||
}
|
||||
</script>
|
||||
104
src/views/bpm/processInstance/detail/TaskAddSignDialogForm.vue
Normal file
104
src/views/bpm/processInstance/detail/TaskAddSignDialogForm.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="加签" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="加签处理人" prop="userIdList">
|
||||
<userSelect v-model="formData.userIdList" v-bind="userVBind" class="w-100%"></userSelect>
|
||||
</el-form-item>
|
||||
<el-form-item label="加签理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入加签理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm('before')">
|
||||
向前加签
|
||||
</el-button>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm('after')">
|
||||
向后加签
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
defineOptions({ name: 'BpmTaskUpdateAssigneeForm' })
|
||||
|
||||
const userStore = useUserStoreWithOut()
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
userIdList: '',
|
||||
type: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
userIdList: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '加签理由不能为空', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
const userVBind = {
|
||||
prop: 'delegateUserId',
|
||||
type: 'edit',
|
||||
column: {
|
||||
label: '加签处理人',
|
||||
findType: 'all',
|
||||
multiple: true,
|
||||
columnKey: ['sex', 'post', 'deptName'],
|
||||
disabledIds: [userStore.user.id]
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
// 获得用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async (type: string) => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
formData.value.type = type
|
||||
try {
|
||||
await TaskApi.taskAddSign(formData.value)
|
||||
message.success('加签成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
userIdList: '',
|
||||
type: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
243
src/views/bpm/processInstance/detail/TaskCCDialogForm.vue
Normal file
243
src/views/bpm/processInstance/detail/TaskCCDialogForm.vue
Normal file
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="修改任务规则" width="600">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
|
||||
<el-form-item label="任务名称" prop="taskName">
|
||||
<el-input v-model="formData.taskName" disabled placeholder="请输入任务名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="任务标识" prop="taskId">
|
||||
<el-input v-model="formData.taskId" disabled placeholder="请输入任务标识" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="processInstanceName">
|
||||
<el-input v-model="formData.processInstanceName" disabled placeholder="请输入流程名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程标识" prop="processInstanceKey">
|
||||
<el-input v-model="formData.processInstanceKey" disabled placeholder="请输入流程标识" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规则类型" prop="type">
|
||||
<el-select v-model="formData.type" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 10" label="指定角色" prop="roleIds">
|
||||
<el-select v-model="formData.roleIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in roleOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type === 20 || formData.type === 21"
|
||||
label="指定部门"
|
||||
prop="deptIds"
|
||||
span="24"
|
||||
>
|
||||
<el-tree-select
|
||||
ref="treeRef"
|
||||
v-model="formData.deptIds"
|
||||
:data="deptTreeOptions"
|
||||
:props="defaultProps"
|
||||
empty-text="加载中,请稍后"
|
||||
multiple
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 22" label="指定岗位" prop="postIds" span="24">
|
||||
<el-select v-model="formData.postIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in postOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.name"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type === 30 || formData.type === 31 || formData.type === 32"
|
||||
label="指定用户"
|
||||
prop="userIds"
|
||||
span="24"
|
||||
>
|
||||
<userSelect v-model="formData.userIds" v-bind="userVBind" class="w-100%"></userSelect>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 40" label="指定用户组" prop="userGroupIds">
|
||||
<el-select v-model="formData.userGroupIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userGroupOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.name"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 50" label="指定脚本" prop="scripts">
|
||||
<el-select v-model="formData.scripts" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in taskAssignScriptDictDatas"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送原因" prop="reason">
|
||||
<el-input v-model="formData.reason" placeholder="请输入抄送原因" type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
type: Number(undefined),
|
||||
taskName: '',
|
||||
taskId: '',
|
||||
processInstanceName: '',
|
||||
processInstanceKey: '',
|
||||
startUserId: '',
|
||||
options: [] as any[],
|
||||
roleIds: [],
|
||||
deptIds: [],
|
||||
postIds: [],
|
||||
userIds: '',
|
||||
userGroupIds: [],
|
||||
scripts: [],
|
||||
reason: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }],
|
||||
roleIds: [{ required: true, message: '指定角色不能为空', trigger: 'change' }],
|
||||
deptIds: [{ required: true, message: '指定部门不能为空', trigger: 'change' }],
|
||||
postIds: [{ required: true, message: '指定岗位不能为空', trigger: 'change' }],
|
||||
userIds: [{ required: true, message: '指定用户不能为空', trigger: 'change' }],
|
||||
userGroupIds: [{ required: true, message: '指定用户组不能为空', trigger: 'change' }],
|
||||
scripts: [{ required: true, message: '指定脚本不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '抄送原因不能为空', trigger: 'change' }]
|
||||
})
|
||||
const userVBind = {
|
||||
prop: 'assigneeUserId',
|
||||
type: 'edit',
|
||||
column: {
|
||||
label: '用户',
|
||||
findType: 'all',
|
||||
multiple: true,
|
||||
columnKey: ['sex', 'post', 'deptName']
|
||||
}
|
||||
}
|
||||
const formRef = ref() // 表单 Ref
|
||||
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
|
||||
const deptTreeOptions = ref() // 部门树
|
||||
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
|
||||
const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (row) => {
|
||||
// 1. 先重置表单
|
||||
resetForm()
|
||||
// 2. 再设置表单
|
||||
if (row != null) {
|
||||
formData.value.type = undefined as unknown as number
|
||||
formData.value.taskName = row.name
|
||||
formData.value.taskId = row.id
|
||||
formData.value.processInstanceName = row.processInstance.name
|
||||
formData.value.processInstanceKey = row.processInstance.id
|
||||
formData.value.startUserId = row.processInstance.startUserId
|
||||
}
|
||||
// 打开弹窗
|
||||
dialogVisible.value = true
|
||||
|
||||
// 获得角色列表
|
||||
roleOptions.value = await RoleApi.getSimpleRoleList()
|
||||
// 获得部门列表
|
||||
deptOptions.value = await DeptApi.getSimpleDeptList()
|
||||
deptTreeOptions.value = handleTree(deptOptions.value, 'id')
|
||||
// 获得岗位列表
|
||||
postOptions.value = await PostApi.getSimplePostList()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 获得用户组列表
|
||||
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 构建表单
|
||||
const form = {
|
||||
...formData.value
|
||||
}
|
||||
// 将 roleIds 等选项赋值到 options 中
|
||||
if (form.type === 10) {
|
||||
form.options = form.roleIds
|
||||
} else if (form.type === 20 || form.type === 21) {
|
||||
form.options = form.deptIds
|
||||
} else if (form.type === 22) {
|
||||
form.options = form.postIds
|
||||
} else if (form.type === 30 || form.type === 31 || form.type === 32) {
|
||||
form.options = form.userIds.split(',')
|
||||
} else if (form.type === 40) {
|
||||
form.options = form.userGroupIds
|
||||
} else if (form.type === 50) {
|
||||
form.options = form.scripts
|
||||
}
|
||||
form.roleIds = undefined
|
||||
form.deptIds = undefined
|
||||
form.postIds = undefined
|
||||
form.userIds = ''
|
||||
form.userGroupIds = undefined
|
||||
form.scripts = undefined
|
||||
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = form as unknown as ProcessInstanceApi.ProcessInstanceCCVO
|
||||
await ProcessInstanceApi.createProcessInstanceCC(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
96
src/views/bpm/processInstance/detail/TaskDelegateForm.vue
Normal file
96
src/views/bpm/processInstance/detail/TaskDelegateForm.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="委派任务" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="接收人" prop="delegateUserId">
|
||||
<userSelect
|
||||
v-model="formData.delegateUserId"
|
||||
v-bind="userVBind"
|
||||
class="w-100%"
|
||||
></userSelect>
|
||||
</el-form-item>
|
||||
<el-form-item label="委派理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入委派理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'BpmTaskDelegateForm' })
|
||||
|
||||
const userStore = useUserStoreWithOut()
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
delegateUserId: undefined
|
||||
})
|
||||
const formRules = ref({
|
||||
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }]
|
||||
})
|
||||
const userVBind = {
|
||||
prop: 'delegateUserId',
|
||||
type: 'edit',
|
||||
column: {
|
||||
label: '接收人',
|
||||
findType: 'all',
|
||||
multiple: false,
|
||||
columnKey: ['sex', 'post', 'deptName'],
|
||||
disabledIds: [userStore.user.id]
|
||||
}
|
||||
}
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
// 获得用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.delegateTask(formData.value)
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
delegateUserId: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="回退" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="退回节点" prop="targetDefinitionKey">
|
||||
<el-select v-model="formData.targetDefinitionKey" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in returnList"
|
||||
:key="item.definitionKey"
|
||||
:label="item.name"
|
||||
:value="item.definitionKey"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="回退理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入回退理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" name="TaskRollbackDialogForm" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
targetDefinitionKey: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
targetDefinitionKey: [{ required: true, message: '必须选择回退节点', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '回退理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const returnList = ref([])
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
returnList.value = await TaskApi.getReturnList({ taskId: id })
|
||||
if (returnList.value.length === 0) {
|
||||
message.warning('当前没有可回退的节点')
|
||||
return false
|
||||
}
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.returnTask(formData.value)
|
||||
message.success('回退成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
targetDefinitionKey: undefined,
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="减签" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="减签任务" prop="id">
|
||||
<el-radio-group v-model="formData.id">
|
||||
<el-radio-button v-for="item in subTaskList" :key="item.id" :label="item.id">
|
||||
{{ item.name }}({{ item.assigneeUser.deptName }}{{ item.assigneeUser.nickname }}--审批)
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="减签理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入减签理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" name="TaskRollbackDialogForm" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
id: [{ required: true, message: '必须选择减签任务', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '减签理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const subTaskList = ref([])
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
subTaskList.value = await TaskApi.getChildrenTaskList(id)
|
||||
if (isEmpty(subTaskList.value)) {
|
||||
message.warning('当前没有可减签的任务')
|
||||
return false
|
||||
}
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.taskSubSign(formData.value)
|
||||
message.success('减签成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="转派审批人" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="新审批人" prop="assigneeUserId">
|
||||
<userSelect
|
||||
v-model="formData.assigneeUserId"
|
||||
v-bind="userVBind"
|
||||
class="w-100%"
|
||||
></userSelect>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'BpmTaskUpdateAssigneeForm' })
|
||||
|
||||
const userStore = useUserStoreWithOut()
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
assigneeUserId: undefined
|
||||
})
|
||||
const formRules = ref({
|
||||
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
const userVBind = {
|
||||
prop: 'assigneeUserId',
|
||||
type: 'edit',
|
||||
column: {
|
||||
label: '新审批人',
|
||||
findType: 'all',
|
||||
multiple: false,
|
||||
columnKey: ['sex', 'post', 'deptName'],
|
||||
disabledIds: [userStore.user.id]
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
formData.value.id = id
|
||||
// 获得用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.updateTaskAssignee(formData.value)
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
assigneeUserId: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
327
src/views/bpm/processInstance/detail/index.vue
Normal file
327
src/views/bpm/processInstance/detail/index.vue
Normal file
@@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 审批信息 -->
|
||||
<el-card
|
||||
v-for="(item, index) in runningTasks"
|
||||
:key="index"
|
||||
v-loading="processInstanceLoading"
|
||||
class="box-card"
|
||||
>
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">审批任务【{{ item.name }}】</span>
|
||||
</template>
|
||||
<el-col :span="24">
|
||||
<el-form
|
||||
:ref="'form' + index"
|
||||
:model="auditForms[index]"
|
||||
:rules="auditRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item v-if="processInstance && processInstance.name" label="流程名">
|
||||
{{ processInstance.name }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人">
|
||||
{{ processInstance.startUser.nickname }}
|
||||
<el-tag size="small" type="info">{{ processInstance.startUser.deptName }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批建议" prop="reason">
|
||||
<el-input
|
||||
v-model="auditForms[index].reason"
|
||||
placeholder="请输入审批建议"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px">
|
||||
<el-button type="success" @click="handleAudit(item, true)">
|
||||
<Icon icon="ep:select" />
|
||||
通过
|
||||
</el-button>
|
||||
<el-button type="danger" @click="handleAudit(item, false)">
|
||||
<Icon icon="ep:close" />
|
||||
不通过
|
||||
</el-button>
|
||||
<el-button type="primary" @click="openTaskUpdateAssigneeForm(item.id)">
|
||||
<Icon icon="ep:edit" />
|
||||
转办
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleDelegate(item)">
|
||||
<Icon icon="ep:position" />
|
||||
委派
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleSign(item)">
|
||||
<Icon icon="ep:plus" />
|
||||
加签
|
||||
</el-button>
|
||||
<el-button type="warning" @click="handleBack(item)">
|
||||
<Icon icon="ep:back" />
|
||||
回退
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-card>
|
||||
|
||||
<div id="Printer">
|
||||
<!-- 申请信息 -->
|
||||
<el-card v-loading="processInstanceLoading" class="box-card"
|
||||
>
|
||||
<template #header>
|
||||
<span class="el-icon-document">申请信息【{{ processInstance.name }}】</span>
|
||||
<el-button type="success" @click="printPage()">
|
||||
<Icon icon="ep:printer" />
|
||||
打印表单
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 情况一:流程表单 -->
|
||||
<el-col v-if="processInstance?.processDefinition?.formType === 10" :span="24">
|
||||
<FormView form-type="view" show-type="view" v-bind="detailForm"></FormView>
|
||||
</el-col>
|
||||
<!-- 情况二:业务表单 -->
|
||||
<div v-if="processInstance?.processDefinition?.formType === 20">
|
||||
<BusinessFormComponent :id="processInstance.businessKey" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 审批记录 -->
|
||||
<ProcessInstanceTaskList :loading="tasksLoad" :tasks="tasks" />
|
||||
</div>
|
||||
<!-- 高亮流程图 -->
|
||||
<ProcessInstanceBpmnViewer
|
||||
:id="`${id}`"
|
||||
:bpmn-xml="bpmnXML"
|
||||
:loading="processInstanceLoading"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
/>
|
||||
|
||||
<!-- 弹窗:转派审批人 -->
|
||||
<TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" />
|
||||
<!-- 弹窗,回退节点 -->
|
||||
<TaskReturnDialog ref="taskReturnDialogRef" @success="getDetail" />
|
||||
<!-- 委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
|
||||
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
|
||||
<!-- 加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
|
||||
<TaskAddSignDialogForm ref="taskAddSignDialogForm" @success="getDetail" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import TaskUpdateAssigneeForm from './TaskUpdateAssigneeForm.vue'
|
||||
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
||||
import TaskReturnDialog from './TaskReturnDialogForm.vue'
|
||||
import TaskDelegateForm from './TaskDelegateForm.vue'
|
||||
import TaskAddSignDialogForm from './TaskAddSignDialogForm.vue'
|
||||
import { registerComponent } from '@/utils/routerHelper'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import router from '@/router/index'
|
||||
import { $Print } from '@smallwei/avue'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||
|
||||
const { query } = useRoute() // 查询参数
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
||||
const userId = useUserStore().getUser.id // 当前登录的编号
|
||||
const id = query.id as unknown as number // 流程实例的编号
|
||||
const processInstanceLoading = ref(false) // 流程实例的加载中
|
||||
const processInstance = ref<any>({}) // 流程实例
|
||||
const bpmnXML = ref('') // BPMN XML
|
||||
const tasksLoad = ref(true) // 任务的加载中
|
||||
const tasks = ref<any[]>([]) // 任务列表
|
||||
// ========== 审批信息 ==========
|
||||
const runningTasks = ref<any[]>([]) // 运行中的任务
|
||||
const auditForms = ref<any[]>([]) // 审批任务的表单
|
||||
const auditRule = reactive({
|
||||
reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
|
||||
})
|
||||
// ========== 申请信息 ==========
|
||||
const detailForm = ref({
|
||||
formId: '',
|
||||
optionsData: {},
|
||||
defaultData: {}
|
||||
})
|
||||
|
||||
/** 处理审批通过和不通过的操作 */
|
||||
const handleAudit = async (task, pass) => {
|
||||
// 1.1 获得对应表单
|
||||
const index = runningTasks.value.indexOf(task)
|
||||
const auditFormRef = proxy.$refs['form' + index][0]
|
||||
// 1.2 校验表单
|
||||
const elForm = unref(auditFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 2.1 提交审批
|
||||
const data = {
|
||||
id: task.id,
|
||||
reason: auditForms.value[index].reason
|
||||
}
|
||||
if (pass) {
|
||||
await TaskApi.approveTask(data)
|
||||
message.success('审批通过成功')
|
||||
} else {
|
||||
await TaskApi.rejectTask(data)
|
||||
message.success('审批不通过成功')
|
||||
}
|
||||
// 2.2 加载最新数据
|
||||
getDetail()
|
||||
}
|
||||
|
||||
const printPage = async () => {
|
||||
|
||||
const { href } = router.resolve({ name: 'BpmProcessInstanceInfo',
|
||||
query: { id: String(id), isPrint: '1' }
|
||||
})
|
||||
window.open(href, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
/** 转派审批人 */
|
||||
const taskUpdateAssigneeFormRef = ref()
|
||||
const openTaskUpdateAssigneeForm = (id: string) => {
|
||||
taskUpdateAssigneeFormRef.value.open(id)
|
||||
}
|
||||
|
||||
const taskDelegateForm = ref()
|
||||
/** 处理审批退回的操作 */
|
||||
const handleDelegate = async (task) => {
|
||||
taskDelegateForm.value.open(task.id)
|
||||
}
|
||||
|
||||
//回退弹框组件
|
||||
const taskReturnDialogRef = ref()
|
||||
/** 处理审批退回的操作 */
|
||||
const handleBack = async (task) => {
|
||||
taskReturnDialogRef.value.open(task.id)
|
||||
}
|
||||
|
||||
const taskAddSignDialogForm = ref()
|
||||
/** 处理审批加签的操作 */
|
||||
const handleSign = async (task) => {
|
||||
taskAddSignDialogForm.value.open(task.id)
|
||||
}
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = () => {
|
||||
// 1. 获得流程实例相关
|
||||
getProcessInstance()
|
||||
// 2. 获得流程任务列表(审批记录)
|
||||
getTaskList()
|
||||
}
|
||||
|
||||
/** 加载流程实例 */
|
||||
const BusinessFormComponent = ref(null) // 异步组件
|
||||
const getProcessInstance = async () => {
|
||||
try {
|
||||
processInstanceLoading.value = true
|
||||
const data = await ProcessInstanceApi.getProcessInstance(id)
|
||||
if (!data) {
|
||||
message.error('查询不到流程信息!')
|
||||
return
|
||||
}
|
||||
processInstance.value = data
|
||||
|
||||
// 设置表单信息
|
||||
const processDefinition = data.processDefinition
|
||||
if (processDefinition.formType === 10) {
|
||||
detailForm.value.formId = processDefinition.formId
|
||||
detailForm.value.optionsData = JSON.parse(processDefinition.formConf)
|
||||
detailForm.value.defaultData = data.formVariables
|
||||
// setConfAndFields2(
|
||||
// detailForm,
|
||||
// processDefinition.formConf,
|
||||
// processDefinition.formFields,
|
||||
// data.formVariables
|
||||
// )
|
||||
// nextTick().then(() => {
|
||||
// fApi.value?.fapi?.btn.show(false)
|
||||
// fApi.value?.fapi?.resetBtn.show(false)
|
||||
// fApi.value?.fapi?.disabled(true)
|
||||
// })
|
||||
} else {
|
||||
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
|
||||
}
|
||||
|
||||
// 加载流程图
|
||||
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(processDefinition.id as number)
|
||||
} finally {
|
||||
processInstanceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载任务列表 */
|
||||
const getTaskList = async () => {
|
||||
try {
|
||||
// 获得未取消的任务
|
||||
tasksLoad.value = true
|
||||
const data = await TaskApi.getTaskListByProcessInstanceId(id)
|
||||
tasks.value = []
|
||||
// 1.1 移除已取消的审批
|
||||
data.forEach((task) => {
|
||||
if (task.result !== 4) {
|
||||
tasks.value.push(task)
|
||||
}
|
||||
})
|
||||
// 1.2 排序,将未完成的排在前面,已完成的排在后面;
|
||||
tasks.value.sort((a, b) => {
|
||||
// 有已完成的情况,按照完成时间倒序
|
||||
if (a.endTime && b.endTime) {
|
||||
return b.endTime - a.endTime
|
||||
} else if (a.endTime) {
|
||||
return 1
|
||||
} else if (b.endTime) {
|
||||
return -1
|
||||
// 都是未完成,按照创建时间倒序
|
||||
} else {
|
||||
return b.createTime - a.createTime
|
||||
}
|
||||
})
|
||||
|
||||
// 获得需要自己审批的任务
|
||||
runningTasks.value = []
|
||||
auditForms.value = []
|
||||
loadRunningTask(tasks.value)
|
||||
} finally {
|
||||
tasksLoad.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 runningTasks 中的任务
|
||||
*/
|
||||
const loadRunningTask = (tasks) => {
|
||||
tasks.forEach((task) => {
|
||||
if (!isEmpty(task.children)) {
|
||||
loadRunningTask(task.children)
|
||||
}
|
||||
// 2.1 只有待处理才需要
|
||||
if (task.result !== 1 && task.result !== 6) {
|
||||
return
|
||||
}
|
||||
// 2.2 自己不是处理人
|
||||
if (!task.assigneeUser || task.assigneeUser.id !== userId) {
|
||||
return
|
||||
}
|
||||
// 2.3 添加到处理任务
|
||||
runningTasks.value.push({ ...task })
|
||||
auditForms.value.push({
|
||||
reason: ''
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async() => {
|
||||
await getDetail()
|
||||
await nextTick()
|
||||
if (query.isPrint === '1') {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
$Print('#Printer')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
254
src/views/bpm/processInstance/index.vue
Normal file
254
src/views/bpm/processInstance/index.vue
Normal file
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #category="{ row }">
|
||||
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="row.category || ''" />
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<template #tasks="scope">
|
||||
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
|
||||
<span>{{ task.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
<template #result="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.result !== undefined"
|
||||
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT"
|
||||
:value="scope.row.result"
|
||||
/>
|
||||
</template>
|
||||
<template #menu-left>
|
||||
<el-button type="primary" v-hasPermi="['bpm:process-instance:query']" @click="handleCreate">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 发起流程
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['bpm:process-instance:cancel']"
|
||||
@click="handleDetail(row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<!-- <el-button
|
||||
link
|
||||
type="danger"
|
||||
v-if="row.result === 1"
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCancel(row)"
|
||||
>
|
||||
取消
|
||||
</el-button> -->
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
|
||||
defineOptions({ name: 'BpmCCProcessInstance' })
|
||||
const router = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 170,
|
||||
column: {
|
||||
id: {
|
||||
label: '流程编号',
|
||||
width: 320
|
||||
},
|
||||
name: {
|
||||
label: '流程名称',
|
||||
search: true
|
||||
},
|
||||
category: {
|
||||
label: '流程分类',
|
||||
search: true,
|
||||
type: 'select',
|
||||
span: 12,
|
||||
dicData: getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY),
|
||||
rules: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }]
|
||||
},
|
||||
tasks: {
|
||||
label: '当前审批任务'
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'select',
|
||||
span: 12,
|
||||
dicData: getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
},
|
||||
result: {
|
||||
label: '结果',
|
||||
search: true,
|
||||
type: 'select',
|
||||
span: 12,
|
||||
dicData: getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT),
|
||||
rules: [{ required: true, message: '结果不能为空', trigger: 'blur' }]
|
||||
},
|
||||
createTime: {
|
||||
label: '提交时间',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
endTime: {
|
||||
label: '结束时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
//表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const permission = getCurrPermi(['bpm:process-instance'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getMyProcessInstancePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
/** 取消按钮操作 */
|
||||
const handleCancel = async (row) => {
|
||||
// 二次确认
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
}
|
||||
|
||||
/** 发起流程操作 **/
|
||||
const handleCreate = () => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceCreate'
|
||||
})
|
||||
}
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
178
src/views/bpm/task/done/index.vue
Normal file
178
src/views/bpm/task/done/index.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu="{ row }">
|
||||
<el-button link type="primary" @click="handleAudit(row)">流程</el-button>
|
||||
</template>
|
||||
<template #result="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.result"
|
||||
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT"
|
||||
:value="scope.row.result"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
defineOptions({ name: 'BpmDoneTask' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
viewBtnIcon: 'none',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
id: {
|
||||
label: '任务编号',
|
||||
width: 300
|
||||
},
|
||||
name: {
|
||||
label: '任务名称',
|
||||
search: true
|
||||
},
|
||||
processInstanceName: {
|
||||
label: '所属流程',
|
||||
bind: 'processInstance.name'
|
||||
},
|
||||
processInstanceStartUserNickname: {
|
||||
label: '流程发起人',
|
||||
bind: 'processInstance.startUserNickname'
|
||||
},
|
||||
result: {
|
||||
label: '状态',
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)
|
||||
},
|
||||
reason: {
|
||||
label: '原因'
|
||||
},
|
||||
searchCreateTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
display: false,
|
||||
hide: true,
|
||||
type: 'daterange',
|
||||
searchRange: true,
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间'
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.searchCreateTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.searchCreateTime)
|
||||
} else delete searchObj.createTime
|
||||
delete searchObj.searchCreateTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await TaskApi.getDoneTaskPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const beforeOpen = (done, type) => {
|
||||
done()
|
||||
}
|
||||
|
||||
/** 处理审批按钮 */
|
||||
const handleAudit = (row) => {
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstance.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
183
src/views/bpm/task/todo/index.vue
Normal file
183
src/views/bpm/task/todo/index.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #suspensionState="scope">
|
||||
<el-tag v-if="scope.row.suspensionState === 1" type="success">激活</el-tag>
|
||||
<el-tag v-if="scope.row.suspensionState === 2" type="warning">挂起</el-tag>
|
||||
</template>
|
||||
<template #processInstanceName="scope">
|
||||
{{ scope.row.processInstance.name }}
|
||||
</template>
|
||||
<template #processInstanceStartUserNickname="scope">
|
||||
{{ scope.row.processInstance.startUserNickname }}
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-button link type="primary" @click="handleAudit(row)">审批</el-button>
|
||||
<!-- <el-button link type="primary" @click="handleCC(row)">抄送</el-button>-->
|
||||
</template>
|
||||
</avue-crud>
|
||||
<TaskCCDialogForm ref="taskCCDialogForm" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import TaskCCDialogForm from '../../processInstance/detail/TaskCCDialogForm.vue'
|
||||
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
defineOptions({ name: 'BpmDoneTask' })
|
||||
|
||||
// const message = useMessage() // 消息弹窗
|
||||
// const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
id: {
|
||||
label: '任务编号',
|
||||
width: 300,
|
||||
display: false
|
||||
},
|
||||
name: {
|
||||
label: '任务名称',
|
||||
search: true
|
||||
},
|
||||
processInstanceName: {
|
||||
label: '所属流程'
|
||||
},
|
||||
processInstanceStartUserNickname: {
|
||||
label: '流程发起人'
|
||||
},
|
||||
suspensionState: {
|
||||
label: '任务状态',
|
||||
|
||||
type: 'select',
|
||||
span: 12,
|
||||
dicData: []
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const popupObj = ref({ detail: false })
|
||||
const currRow = ref<any>({})
|
||||
const permission = getCurrPermi(['bpm:task'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await TaskApi.getTodoTaskPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 处理审批按钮 */
|
||||
const handleAudit = (row) => {
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstance.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const taskCCDialogForm = ref()
|
||||
/** 处理抄送按钮 */
|
||||
const handleCC = (row) => {
|
||||
taskCCDialogForm.value.open(row)
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
250
src/views/bpm/taskAssignRule/TaskAssignRuleForm.vue
Normal file
250
src/views/bpm/taskAssignRule/TaskAssignRuleForm.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="修改任务规则" width="600">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
|
||||
<el-form-item label="任务名称" prop="taskDefinitionName">
|
||||
<el-input v-model="formData.taskDefinitionName" disabled placeholder="请输入流标标识" />
|
||||
</el-form-item>
|
||||
<el-form-item label="任务标识" prop="taskDefinitionKey">
|
||||
<el-input v-model="formData.taskDefinitionKey" disabled placeholder="请输入任务标识" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规则类型" prop="type">
|
||||
<el-select v-model="formData.type" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 10" label="指定角色" prop="roleIds">
|
||||
<el-select v-model="formData.roleIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in roleOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type === 20 || formData.type === 21"
|
||||
label="指定部门"
|
||||
prop="deptIds"
|
||||
span="24"
|
||||
>
|
||||
<el-tree-select
|
||||
ref="treeRef"
|
||||
v-model="formData.deptIds"
|
||||
:data="deptTreeOptions"
|
||||
:props="defaultProps"
|
||||
empty-text="加载中,请稍后"
|
||||
multiple
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 22" label="指定岗位" prop="postIds" span="24">
|
||||
<el-select v-model="formData.postIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in postOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.name"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type === 30 || formData.type === 31 || formData.type === 32"
|
||||
label="指定用户"
|
||||
prop="userIds"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="formData.userIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.nickname"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 40" label="指定用户组" prop="userGroupIds">
|
||||
<el-select v-model="formData.userGroupIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userGroupOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.name"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 50" label="指定脚本" prop="scripts">
|
||||
<el-select v-model="formData.scripts" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in taskAssignScriptDictDatas"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
|
||||
defineOptions({ name: 'BpmTaskAssignRuleForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
type: Number(undefined),
|
||||
modelId: '',
|
||||
options: [],
|
||||
roleIds: [],
|
||||
deptIds: [],
|
||||
postIds: [],
|
||||
userIds: [],
|
||||
userGroupIds: [],
|
||||
scripts: []
|
||||
})
|
||||
const formRules = reactive({
|
||||
type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }],
|
||||
roleIds: [{ required: true, message: '指定角色不能为空', trigger: 'change' }],
|
||||
deptIds: [{ required: true, message: '指定部门不能为空', trigger: 'change' }],
|
||||
postIds: [{ required: true, message: '指定岗位不能为空', trigger: 'change' }],
|
||||
userIds: [{ required: true, message: '指定用户不能为空', trigger: 'change' }],
|
||||
userGroupIds: [{ required: true, message: '指定用户组不能为空', trigger: 'change' }],
|
||||
scripts: [{ required: true, message: '指定脚本不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
|
||||
const deptTreeOptions = ref() // 部门树
|
||||
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
|
||||
const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (modelId: string, row: TaskAssignRuleApi.TaskAssignVO) => {
|
||||
// 1. 先重置表单
|
||||
resetForm()
|
||||
// 2. 再设置表单
|
||||
formData.value = {
|
||||
...row,
|
||||
modelId: modelId,
|
||||
options: [],
|
||||
roleIds: [],
|
||||
deptIds: [],
|
||||
postIds: [],
|
||||
userIds: [],
|
||||
userGroupIds: [],
|
||||
scripts: []
|
||||
}
|
||||
// 将 options 赋值到对应的 roleIds 等选项
|
||||
if (row.type === 10) {
|
||||
formData.value.roleIds.push(...row.options)
|
||||
} else if (row.type === 20 || row.type === 21) {
|
||||
formData.value.deptIds.push(...row.options)
|
||||
} else if (row.type === 22) {
|
||||
formData.value.postIds.push(...row.options)
|
||||
} else if (row.type === 30 || row.type === 31 || row.type === 32) {
|
||||
formData.value.userIds.push(...row.options)
|
||||
} else if (row.type === 40) {
|
||||
formData.value.userGroupIds.push(...row.options)
|
||||
} else if (row.type === 50) {
|
||||
formData.value.scripts.push(...row.options)
|
||||
}
|
||||
// 打开弹窗
|
||||
dialogVisible.value = true
|
||||
|
||||
// 获得角色列表
|
||||
roleOptions.value = await RoleApi.getSimpleRoleList()
|
||||
// 获得部门列表
|
||||
deptOptions.value = await DeptApi.getSimpleDeptList()
|
||||
deptTreeOptions.value = handleTree(deptOptions.value, 'id')
|
||||
// 获得岗位列表
|
||||
postOptions.value = await PostApi.getSimplePostList()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 获得用户组列表
|
||||
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 构建表单
|
||||
const form = {
|
||||
...formData.value,
|
||||
taskDefinitionName: undefined
|
||||
}
|
||||
// 将 roleIds 等选项赋值到 options 中
|
||||
if (form.type === 10) {
|
||||
form.options = form.roleIds
|
||||
} else if (form.type === 20 || form.type === 21) {
|
||||
form.options = form.deptIds
|
||||
} else if (form.type === 22) {
|
||||
form.options = form.postIds
|
||||
} else if (form.type === 30 || form.type === 31 || form.type === 32) {
|
||||
form.options = form.userIds
|
||||
} else if (form.type === 40) {
|
||||
form.options = form.userGroupIds
|
||||
} else if (form.type === 50) {
|
||||
form.options = form.scripts
|
||||
}
|
||||
form.roleIds = undefined
|
||||
form.deptIds = undefined
|
||||
form.postIds = undefined
|
||||
form.userIds = undefined
|
||||
form.userGroupIds = undefined
|
||||
form.scripts = undefined
|
||||
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = form as unknown as TaskAssignRuleApi.TaskAssignVO
|
||||
if (!data.id) {
|
||||
await TaskAssignRuleApi.createTaskAssignRule(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await TaskAssignRuleApi.updateTaskAssignRule(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
278
src/views/bpm/taskAssignRule/index.vue
Normal file
278
src/views/bpm/taskAssignRule/index.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
:data="tableData"
|
||||
:permission="permission"
|
||||
:option="tableOption"
|
||||
:table-loading="loading"
|
||||
@row-update="rowUpdate"
|
||||
@refresh-change="getTableData"
|
||||
>
|
||||
<template #deptIds-form="{ column, disabled }">
|
||||
<DeptSelect
|
||||
v-model="tableForm.deptIds"
|
||||
:column="column"
|
||||
:disabled="disabled"
|
||||
type="edit"
|
||||
prop="deptIds"
|
||||
></DeptSelect>
|
||||
</template>
|
||||
<template #userIds-form="{ column, disabled }">
|
||||
<UserSelect
|
||||
v-model="tableForm.userIds"
|
||||
:column="column"
|
||||
:disabled="disabled"
|
||||
type="edit"
|
||||
prop="userIds"
|
||||
></UserSelect>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
import { setUserAndDeptName } from '@/components/LowDesign/src/utils/getName'
|
||||
import { useLowStoreWithOut } from '@/store/modules/low'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
const lowStore = useLowStoreWithOut()
|
||||
|
||||
defineOptions({ name: 'BpmTaskAssignRule' })
|
||||
|
||||
const { query } = useRoute() // 查询参数
|
||||
|
||||
const loading = ref(true)
|
||||
const getRules = (label) => {
|
||||
return [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择' + label,
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
const getUserKey = (val) => {
|
||||
let key = ''
|
||||
if (val == 10) key = 'roleIds'
|
||||
else if ([20, 21].includes(val)) key = 'deptIds'
|
||||
else if (val == 22) key = 'postIds'
|
||||
else if ([30, 31, 32].includes(val)) key = 'userIds'
|
||||
else if (val == 40) key = 'userGroupIds'
|
||||
else if (val == 50) key = 'scripts'
|
||||
return key
|
||||
}
|
||||
const tableOption = reactive({
|
||||
border: true,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
labelWidth: 120,
|
||||
dialogWidth: 500,
|
||||
addBtn: false,
|
||||
delBtn: false,
|
||||
calcHeight: 20,
|
||||
column: {
|
||||
taskDefinitionName: { label: '任务名', disabled: true },
|
||||
taskDefinitionKey: { label: '任务标识', disabled: true },
|
||||
type: {
|
||||
label: '规则类型',
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE),
|
||||
rules: getRules('规则类型'),
|
||||
control: (val) => {
|
||||
const columnObj = {
|
||||
roleIds: { display: false },
|
||||
deptIds: { display: false },
|
||||
postIds: { display: false },
|
||||
userIds: { display: false },
|
||||
userGroupIds: { display: false },
|
||||
scripts: { display: false }
|
||||
}
|
||||
const key = getUserKey(val)
|
||||
if (key) columnObj[key].display = true
|
||||
return columnObj
|
||||
}
|
||||
},
|
||||
options: {
|
||||
label: '规则范围',
|
||||
display: false,
|
||||
formatter: (row) => {
|
||||
if (row.ruleModel == 'startEvent') return '-'
|
||||
if (!row.type) return '未指定'
|
||||
const key = getUserKey(row.type)
|
||||
let arr = row.options.map((id) => {
|
||||
if (key == 'deptIds') return lowStore.dicObj.deptSelect?.[id] || id
|
||||
else if (key == 'userIds') return lowStore.dicObj.userSelect?.[id] || id
|
||||
else return dicObj.value[key][id]
|
||||
})
|
||||
|
||||
return `${row.$type}:${arr.join('、')}`
|
||||
}
|
||||
},
|
||||
|
||||
roleIds: {
|
||||
label: '指定角色',
|
||||
type: 'select',
|
||||
dicData: [],
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: getRules('指定角色')
|
||||
},
|
||||
deptIds: {
|
||||
label: '指定部门',
|
||||
hide: true,
|
||||
display: false,
|
||||
findType: 'all',
|
||||
multiple: true,
|
||||
checkStrictly: true,
|
||||
rules: getRules('指定部门')
|
||||
},
|
||||
postIds: {
|
||||
label: '指定岗位',
|
||||
type: 'select',
|
||||
dicData: [],
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: getRules('指定岗位')
|
||||
},
|
||||
userIds: {
|
||||
label: '指定用户',
|
||||
hide: true,
|
||||
display: false,
|
||||
findType: 'all',
|
||||
multiple: true,
|
||||
columnKey: ['sex', 'post', 'deptName'],
|
||||
rules: getRules('指定用户')
|
||||
},
|
||||
userGroupIds: {
|
||||
label: '指定用户组',
|
||||
type: 'select',
|
||||
dicData: [],
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: getRules('指定用户组')
|
||||
},
|
||||
scripts: {
|
||||
label: '指定脚本',
|
||||
type: 'select',
|
||||
dicData: [],
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: getRules('指定脚本')
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref<any[]>([])
|
||||
const dicObj = ref<any>({})
|
||||
|
||||
const permission = getCurrPermi(['bpm:task-assign-rule'])
|
||||
const crudRef = ref()
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await TaskAssignRuleApi.getTaskAssignRuleList({
|
||||
modelId: query.modelId,
|
||||
processDefinitionId: query.processDefinitionId
|
||||
})
|
||||
tableData.value = await formattingTableData(data)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
const key = getUserKey(form.type)
|
||||
const delKey = ['roleIds', 'deptIds', 'postIds', 'userIds', 'userGroupIds', 'scripts']
|
||||
if (key) {
|
||||
if (typeof form[key] == 'number') form[key] = form[key] + ''
|
||||
const value = form[key]
|
||||
form.options = typeof value == 'string' ? value.split(',') : value
|
||||
}
|
||||
delKey.forEach((prop) => delete form[prop])
|
||||
if (!form.modelId) form.modelId = query.modelId
|
||||
const apiName = form.id ? 'updateTaskAssignRule' : 'createTaskAssignRule'
|
||||
let bool = await TaskAssignRuleApi[apiName](form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
const getDicData = () => {
|
||||
return new Promise(async (resolve) => {
|
||||
const keyList = ['roleIds', 'postIds', 'userGroupIds']
|
||||
const promiseArr = [
|
||||
RoleApi.getSimpleRoleList(),
|
||||
PostApi.getSimplePostList(),
|
||||
UserGroupApi.getSimpleUserGroupList()
|
||||
]
|
||||
const resData = await Promise.all(promiseArr)
|
||||
resData.forEach((data, index) => {
|
||||
const key = keyList[index]
|
||||
dicObj.value[key] = {}
|
||||
tableOption.column[key].dicData = data.map((item) => {
|
||||
const id = item.id + ''
|
||||
const name = item.name
|
||||
dicObj.value[key][id] = name
|
||||
return { label: name, value: id }
|
||||
})
|
||||
})
|
||||
dicObj.value.scripts = {}
|
||||
tableOption.column.scripts.dicData = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT).map(
|
||||
(item) => {
|
||||
dicObj.value.scripts[item.value] = item.label
|
||||
return { value: item.value + '', label: item.label }
|
||||
}
|
||||
) as never[]
|
||||
dicObj.value.type = {}
|
||||
tableOption.column.type.dicData.forEach((item) => {
|
||||
dicObj.value.type[item.value] = item.label
|
||||
})
|
||||
resolve(true)
|
||||
})
|
||||
}
|
||||
const formattingTableData = (data): Promise<any[]> => {
|
||||
return new Promise(async (resolve) => {
|
||||
const deptIdList: any[] = []
|
||||
const userIdList: any[] = []
|
||||
data = data.map((item) => {
|
||||
const key = getUserKey(item.type)
|
||||
item.$type = dicObj.value['type'][item.type]
|
||||
if (key) {
|
||||
if (!item.options) item.options = []
|
||||
item[key] = item.options.join(',')
|
||||
if (['deptIds', 'userIds'].includes(key)) {
|
||||
if (key == 'deptIds') deptIdList.push(...item.options)
|
||||
else userIdList.push(...item.options)
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
await setUserAndDeptName({ userIdList, deptIdList })
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
await getDicData()
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
90
src/views/document/TabsCardDocument.vue
Normal file
90
src/views/document/TabsCardDocument.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<el-tabs v-model="activeName" type="border-card" class="demo-tabs" @tab-click="handleClick">
|
||||
<template v-for="item in tabsPaneList" :key="item.name">
|
||||
<el-tab-pane :label="item.label" :name="item.name">
|
||||
<LowTable
|
||||
:ref="(el) => (tableRef[item.name] = el)"
|
||||
:tableId="item.formId"
|
||||
:calcHeight="item.calcHeight || undefined"
|
||||
:enhanceData="item.enhanceData"
|
||||
:fixed-search="item.fixedSearch || {}"
|
||||
></LowTable>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 引入依赖低代码的table代码,这样就可以动态引入表格
|
||||
import {LowTable} from "@/components/LowDesign";
|
||||
|
||||
// 定义该组件的选项
|
||||
defineOptions({name: 'TabsCardDocument'})
|
||||
|
||||
const activeName = ref('institution')
|
||||
const tableRef = ref({})
|
||||
|
||||
const tabsPaneList = ref([
|
||||
{
|
||||
label: '制度',
|
||||
name: 'institution',
|
||||
formId: '1966386366515343361',
|
||||
calcHeight: 200,
|
||||
enhanceData: {hideHeader: 'disabled'},
|
||||
fixedSearch: {file_main_type: 0}
|
||||
},
|
||||
{
|
||||
label: '预案',
|
||||
name: 'plan',
|
||||
formId: '1966386366515343361',
|
||||
calcHeight: 200,
|
||||
fixedSearch: {file_main_type: 1}
|
||||
},
|
||||
{
|
||||
label: '标准',
|
||||
name: 'standard',
|
||||
formId: '1966386366515343361',
|
||||
calcHeight: 200,
|
||||
fixedSearch: {file_main_type: 2}
|
||||
},
|
||||
{
|
||||
label: '规程',
|
||||
name: 'regulations',
|
||||
formId: '1966386366515343361',
|
||||
calcHeight: 200,
|
||||
fixedSearch: {file_main_type: 3}
|
||||
},
|
||||
{
|
||||
label: '目标责任',
|
||||
name: 'responsibility',
|
||||
formId: '1966386366515343361',
|
||||
calcHeight: 200,
|
||||
fixedSearch: {file_main_type: 4}
|
||||
},
|
||||
])
|
||||
|
||||
// 定义点击tab的事件动作
|
||||
const handleClick = (tab) => {
|
||||
const key = tab.props.name
|
||||
tableRef.value[key].initTableLayout()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.demo-tabs > .el-tabs__content {
|
||||
padding: 32px;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #6b778c;
|
||||
}
|
||||
|
||||
.demo-tabs {
|
||||
::v-deep(.el-tabs__nav-wrap) {
|
||||
.el-tabs__item {
|
||||
height: 50px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
262
src/views/infra/config/index.vue
Normal file
262
src/views/infra/config/index.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:config:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #accountCount="scope">
|
||||
<el-tag>{{ scope.row.accountCount }}</el-tag>
|
||||
</template>
|
||||
<template #visible="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.visible !== undefined"
|
||||
:type="DICT_TYPE.INFRA_BOOLEAN_STRING"
|
||||
:value="scope.row.visible"
|
||||
/>
|
||||
</template>
|
||||
<template #type="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.type !== undefined"
|
||||
:type="DICT_TYPE.INFRA_CONFIG_TYPE"
|
||||
:value="scope.row.type"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 12,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 180,
|
||||
column: {
|
||||
category: {
|
||||
label: '参数分类',
|
||||
width: 90,
|
||||
rules: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }]
|
||||
},
|
||||
name: {
|
||||
label: '参数名称',
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
key: {
|
||||
label: '参数键名',
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
value: {
|
||||
label: '参数键值',
|
||||
rules: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }]
|
||||
},
|
||||
visible: {
|
||||
label: '是否可见',
|
||||
type: 'radio',
|
||||
width: 85,
|
||||
dicData: getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING),
|
||||
rules: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||
value: true
|
||||
},
|
||||
type: {
|
||||
label: '系统内置',
|
||||
search: true,
|
||||
width: 95,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE),
|
||||
display: false
|
||||
},
|
||||
remark: {
|
||||
label: '备注',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
span: 24,
|
||||
maxRows: 4,
|
||||
overHidden: true
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 160,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['infra:config'])
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await ConfigApi.getConfigPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await ConfigApi.getConfig(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await ConfigApi.createConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await ConfigApi.updateConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ConfigApi.deleteConfig(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await ConfigApi.exportConfig(searchObj)
|
||||
download.excel(data, '配置管理列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
268
src/views/infra/dataSourceConfig/index.vue
Normal file
268
src/views/infra/dataSourceConfig/index.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row, index }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="crudRef.rowEdit(row, index)"
|
||||
v-hasPermi="['infra:data-source-config:update']"
|
||||
v-if="row.id !== 0"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="crudRef.rowDel(row, index)"
|
||||
v-hasPermi="['infra:data-source-config:delete']"
|
||||
v-if="row.id !== 0"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #dbCode="{ row }">
|
||||
<span> {{ row.dbCode }} </span>
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="row.isConnect == 'Y' ? 'primary' : 'danger'"
|
||||
effect="dark"
|
||||
class="pos-absolute right-2px top-2px"
|
||||
>
|
||||
{{ row.isConnect == 'Y' ? '已连接' : '已断开' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
|
||||
|
||||
defineOptions({ name: 'InfraDataSourceConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
|
||||
const DBList = [
|
||||
{
|
||||
label: 'MySQL 5.7+',
|
||||
value: 'MySQL',
|
||||
driverClass: 'com.mysql.cj.jdbc.Driver',
|
||||
url: 'jdbc:mysql://127.0.0.1:3306/jeelowcode?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai'
|
||||
},
|
||||
{
|
||||
label: 'Oracle',
|
||||
value: 'Oracle',
|
||||
driverClass: 'oracle.jdbc.OracleDriver',
|
||||
url: 'jdbc:oracle:thin:@127.0.0.1:1521:ORCL'
|
||||
},
|
||||
{
|
||||
label: 'postgresql',
|
||||
value: 'postgresql',
|
||||
driverClass: 'org.postgresql.Driver',
|
||||
url: 'jdbc:postgresql://127.0.0.1:5432/jeelowcode'
|
||||
},
|
||||
{
|
||||
label: '达梦',
|
||||
value: 'DM',
|
||||
driverClass: 'dm.jdbc.driver.DmDriver',
|
||||
url: 'jdbc:dm://127.0.0.1:5236/?jeelowcode&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8'
|
||||
}
|
||||
]
|
||||
|
||||
const tableOption = reactive({
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 120,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
dbCode: {
|
||||
label: '数据源编码',
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '数据源编码不能为空', trigger: 'blur' }],
|
||||
editDisabled: true
|
||||
},
|
||||
name: {
|
||||
label: '数据源名称',
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '数据源名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
dbType: {
|
||||
label: '数据库类型',
|
||||
type: 'select',
|
||||
dicData: DBList,
|
||||
width: 120,
|
||||
rules: [{ required: true, message: '数据库类型不能为空', trigger: 'change' }],
|
||||
change: ({ item }) => {
|
||||
if (item) {
|
||||
tableForm.value.driverClass = item.driverClass
|
||||
tableForm.value.url = item.url
|
||||
}
|
||||
}
|
||||
},
|
||||
driverClass: {
|
||||
label: '驱动类',
|
||||
hide: true,
|
||||
rules: [{ required: true, message: '驱动类不能为空', trigger: 'blur' }]
|
||||
},
|
||||
url: {
|
||||
label: '数据源连接',
|
||||
type: 'textarea',
|
||||
minRows: 1,
|
||||
maxRows: 3,
|
||||
overHidden: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '数据源连接不能为空', trigger: 'blur' }]
|
||||
},
|
||||
username: {
|
||||
label: '用户名',
|
||||
width: 120,
|
||||
rules: [{ required: true, message: '用户名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
password: {
|
||||
label: '密码',
|
||||
rules: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
|
||||
hide: true
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 160,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['infra:data-source-config'])
|
||||
|
||||
const crudRef = ref()
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await DataSourceConfigApi.getDataSourceConfigList()
|
||||
tableData.value = data
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await DataSourceConfigApi.getDataSourceConfig(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await DataSourceConfigApi.createDataSourceConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await DataSourceConfigApi.updateDataSourceConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await DataSourceConfigApi.deleteDataSourceConfig(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
57
src/views/infra/dbDoc/index.vue
Normal file
57
src/views/infra/dbDoc/index.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<ContentWrap title="数据库文档">
|
||||
<div class="mb-10px">
|
||||
<el-button type="primary" plain @click="handleExport('HTML')">
|
||||
<Icon icon="ep:download" /> 导出 HTML
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="handleExport('Word')">
|
||||
<Icon icon="ep:download" /> 导出 Word
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="handleExport('Markdown')">
|
||||
<Icon icon="ep:download" /> 导出 Markdown
|
||||
</el-button>
|
||||
</div>
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import download from '@/utils/download'
|
||||
import * as DbDocApi from '@/api/infra/dbDoc'
|
||||
|
||||
defineOptions({ name: 'InfraDBDoc' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref('') // HTML 的地址
|
||||
|
||||
/** 页面加载 */
|
||||
const init = async () => {
|
||||
try {
|
||||
const data = await DbDocApi.exportHtml()
|
||||
const blob = new Blob([data], { type: 'text/html' })
|
||||
src.value = window.URL.createObjectURL(blob)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理导出 */
|
||||
const handleExport = async (type: string) => {
|
||||
if (type === 'HTML') {
|
||||
const res = await DbDocApi.exportHtml()
|
||||
download.html(res, '数据库文档.html')
|
||||
}
|
||||
if (type === 'Word') {
|
||||
const res = await DbDocApi.exportWord()
|
||||
download.word(res, '数据库文档.doc')
|
||||
}
|
||||
if (type === 'Markdown') {
|
||||
const res = await DbDocApi.exportMarkdown()
|
||||
download.markdown(res, '数据库文档.md')
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
})
|
||||
</script>
|
||||
25
src/views/infra/druid/index.vue
Normal file
25
src/views/infra/druid/index.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="url" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraDruid' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const url = ref(import.meta.env.VITE_BASE_URL + '/druid/index.html')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.druid')
|
||||
if (data && data.length > 0) {
|
||||
url.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
103
src/views/infra/file/FileForm.vue
Normal file
103
src/views/infra/file/FileForm.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<DesignPopup v-model="dialogVisible" title="上传文件" width="40%" :is-footer="true">
|
||||
<div class="p-20px">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:action="uploadUrl"
|
||||
:auto-upload="false"
|
||||
:data="data"
|
||||
:disabled="formLoading"
|
||||
:limit="1"
|
||||
:on-change="handleFileChange"
|
||||
:on-error="submitFormError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitFormSuccess"
|
||||
:http-request="httpRequest"
|
||||
accept=".jpg, .png, .gif"
|
||||
drag
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip" style="color: red">
|
||||
提示:仅允许导入 jpg、png、gif 格式文件!
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||
|
||||
defineOptions({ name: 'InfraFileForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const fileList = ref([]) // 文件列表
|
||||
const data = ref({ path: '' })
|
||||
const uploadRef = ref()
|
||||
|
||||
const { uploadUrl, httpRequest } = useUpload()
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 处理上传的文件发生变化 */
|
||||
const handleFileChange = (file) => {
|
||||
data.value.path = file.name
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const submitFileForm = () => {
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
unref(uploadRef)?.submit()
|
||||
}
|
||||
|
||||
/** 文件上传成功处理 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitFormSuccess = () => {
|
||||
// 清理
|
||||
dialogVisible.value = false
|
||||
formLoading.value = false
|
||||
unref(uploadRef)?.clearFiles()
|
||||
// 提示成功,并刷新
|
||||
setTimeout(() => {
|
||||
message.success(t('common.createSuccess'))
|
||||
emit('success')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('上传失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
</script>
|
||||
210
src/views/infra/file/index.vue
Normal file
210
src/views/infra/file/index.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button type="primary" plain @click="openForm">
|
||||
<Icon icon="ep:upload" class="mr-5px" /> 上传文件
|
||||
</el-button>
|
||||
</template>
|
||||
<template #urlValue="scope">
|
||||
{{ scope.row.url }}
|
||||
</template>
|
||||
<template #url="{ row }">
|
||||
<el-image
|
||||
v-if="row.type.includes('image')"
|
||||
class="h-80px w-80px"
|
||||
lazy
|
||||
:src="row.url"
|
||||
:preview-src-list="[row.url]"
|
||||
preview-teleported
|
||||
fit="cover"
|
||||
/>
|
||||
<el-link
|
||||
v-else-if="row.type.includes('pdf')"
|
||||
type="primary"
|
||||
:href="row.url"
|
||||
:underline="false"
|
||||
target="_blank"
|
||||
>预览</el-link
|
||||
>
|
||||
<el-link v-else type="primary" download :href="row.url" :underline="false" target="_blank"
|
||||
>下载</el-link
|
||||
>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<FileForm ref="formRef" @success="getTableData" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { fileSizeFormatter } from '@/utils'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as FileApi from '@/api/infra/file'
|
||||
import FileForm from './FileForm.vue'
|
||||
|
||||
defineOptions({ name: 'InfraFile' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
name: {
|
||||
label: '文件名',
|
||||
minWidth: 120,
|
||||
overHidden: true
|
||||
},
|
||||
path: {
|
||||
label: '文件路径',
|
||||
minWidth: 120,
|
||||
search: true,
|
||||
overHidden: true
|
||||
},
|
||||
urlValue: {
|
||||
label: 'URL',
|
||||
minWidth: 100,
|
||||
overHidden: true
|
||||
},
|
||||
size: {
|
||||
label: '文件大小',
|
||||
minWidth: 100,
|
||||
formatter: fileSizeFormatter,
|
||||
width: '120px'
|
||||
},
|
||||
type: {
|
||||
label: '文件类型',
|
||||
minWidth: 120,
|
||||
search: true,
|
||||
overHidden: true
|
||||
},
|
||||
url: {
|
||||
label: '文件内容',
|
||||
width: 120
|
||||
},
|
||||
createTime: {
|
||||
label: '上传时间',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 160,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['infra:file'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await FileApi.getFilePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await FileApi.deleteFile(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = () => {
|
||||
formRef.value.open()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
452
src/views/infra/fileConfig/index.vue
Normal file
452
src/views/infra/fileConfig/index.vue
Normal file
@@ -0,0 +1,452 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<template #storage="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.INFRA_FILE_STORAGE"
|
||||
:value="scope.row.storage ? scope.row.storage : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #master="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.master !== undefined"
|
||||
:type="DICT_TYPE.INFRA_BOOLEAN_STRING"
|
||||
:value="scope.row.master"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
class="is-text"
|
||||
icon="el-icon-operation"
|
||||
:disabled="row.master"
|
||||
@click="handleMaster(row.id)"
|
||||
v-hasPermi="['infra:file-config:update']"
|
||||
>
|
||||
主配置
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
class="is-text"
|
||||
icon="el-icon-tickets"
|
||||
type="primary"
|
||||
@click="handleTest(row.id)"
|
||||
>
|
||||
测试
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as FileConfigApi from '@/api/infra/fileConfig'
|
||||
|
||||
defineOptions({ name: 'InfraFileConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
menuWidth: 300,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 100,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
id: {
|
||||
label: '编号',
|
||||
width: 80,
|
||||
display: false
|
||||
},
|
||||
name: {
|
||||
label: '配置名',
|
||||
search: true,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '配置名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
remark: {
|
||||
label: '备注'
|
||||
},
|
||||
storage: {
|
||||
label: '存储器',
|
||||
type: 'select',
|
||||
disabled: false,
|
||||
search: true,
|
||||
span: 8,
|
||||
minWidth: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE),
|
||||
rules: [{ required: true, message: '存储器不能为空', trigger: 'blur' }],
|
||||
change: ({ value, column }) => {
|
||||
let {
|
||||
basePath,
|
||||
host,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
mode,
|
||||
endpoint,
|
||||
bucket,
|
||||
accessKey,
|
||||
accessSecret,
|
||||
domain
|
||||
} = tableOption.column
|
||||
if (value) {
|
||||
domain.display = true
|
||||
if (value === 20) {
|
||||
domain.rules = []
|
||||
} else {
|
||||
domain.rules = [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }]
|
||||
}
|
||||
} else {
|
||||
domain.display = false
|
||||
}
|
||||
if (value >= 10 && value <= 12) {
|
||||
basePath.display = true
|
||||
} else {
|
||||
basePath.display = false
|
||||
}
|
||||
if (value >= 11 && value <= 12) {
|
||||
host.display = true
|
||||
port.display = true
|
||||
username.display = true
|
||||
password.display = true
|
||||
} else {
|
||||
host.display = false
|
||||
port.display = false
|
||||
username.display = false
|
||||
password.display = false
|
||||
}
|
||||
if (value === 11) {
|
||||
mode.display = true
|
||||
} else {
|
||||
mode.display = false
|
||||
}
|
||||
if (value === 20) {
|
||||
endpoint.display = true
|
||||
bucket.display = true
|
||||
accessKey.display = true
|
||||
accessSecret.display = true
|
||||
} else {
|
||||
endpoint.display = false
|
||||
bucket.display = false
|
||||
accessKey.display = false
|
||||
accessSecret.display = false
|
||||
}
|
||||
}
|
||||
},
|
||||
master: {
|
||||
label: '主配置',
|
||||
type: 'select',
|
||||
display: false,
|
||||
span: 8,
|
||||
minWidth: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)
|
||||
},
|
||||
basePath: {
|
||||
label: '基础路径',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '基础路径不能为空', trigger: 'blur' }]
|
||||
},
|
||||
host: {
|
||||
label: '主机地址',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '主机地址不能为空', trigger: 'blur' }]
|
||||
},
|
||||
port: {
|
||||
label: '主机端口',
|
||||
type: 'number',
|
||||
span: 8,
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '主机端口不能为空', trigger: 'blur' }]
|
||||
},
|
||||
username: {
|
||||
label: '用户名',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '用户名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
password: {
|
||||
label: '密码',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '密码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
mode: {
|
||||
label: '连接模式',
|
||||
type: 'radio',
|
||||
hide: true,
|
||||
display: false,
|
||||
dicData: [
|
||||
{ label: '主动模式', value: 'Active' },
|
||||
{ label: '被动模式', value: 'Passive' }
|
||||
],
|
||||
rules: [{ required: true, message: '连接模式不能为空', trigger: 'blur' }]
|
||||
},
|
||||
endpoint: {
|
||||
label: '节点地址',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '节点地址不能为空', trigger: 'blur' }]
|
||||
},
|
||||
bucket: {
|
||||
label: '存储 bucket',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '存储 bucket不能为空', trigger: 'blur' }]
|
||||
},
|
||||
accessKey: {
|
||||
label: 'accessKey',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: 'accessKey不能为空', trigger: 'blur' }]
|
||||
},
|
||||
accessSecret: {
|
||||
label: 'accessSecret',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: 'accessSecret不能为空', trigger: 'blur' }]
|
||||
},
|
||||
domain: {
|
||||
label: '自定义域名',
|
||||
hide: true,
|
||||
display: false,
|
||||
rules: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
let tableChildForm: string[] = reactive([]) // 保存参数,方便提交
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['infra:file-config'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await FileConfigApi.getFileConfigPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableOption.column.storage.disabled = true
|
||||
let data = await FileConfigApi.getFileConfig(tableForm.value.id)
|
||||
tableChildForm = Object.keys(data.config)
|
||||
// 数据处理
|
||||
data = {
|
||||
storage: data.storage,
|
||||
remark: data.remark,
|
||||
name: data.name,
|
||||
master: data.master,
|
||||
id: data.id,
|
||||
createTime: data.createTime,
|
||||
...data.config
|
||||
}
|
||||
tableForm.value = data
|
||||
} else {
|
||||
tableOption.column.storage.disabled = false
|
||||
}
|
||||
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
// 删除多余表单
|
||||
Object.keys(tableOption.column).forEach((item) => {
|
||||
if (tableOption.column[item].display != undefined && !tableOption.column[item].display)
|
||||
delete form[item]
|
||||
})
|
||||
|
||||
// 添加config参数
|
||||
let config = {}
|
||||
Object.keys(form).forEach((item) => {
|
||||
if (item !== 'storage' && item !== 'remark' && item !== 'name') {
|
||||
config[item] = form[item]
|
||||
delete form[item]
|
||||
}
|
||||
})
|
||||
form.config = config
|
||||
// 发送请求
|
||||
let bool = await FileConfigApi.createFileConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
// 添加config参数
|
||||
let config = {}
|
||||
tableChildForm.forEach((item) => {
|
||||
if (form[item]) {
|
||||
config[item] = form[item]
|
||||
}
|
||||
})
|
||||
Object.keys(form).forEach((item) => {
|
||||
if (
|
||||
item !== 'storage' &&
|
||||
item !== 'remark' &&
|
||||
item !== 'name' &&
|
||||
item !== 'master' &&
|
||||
item !== 'id' &&
|
||||
item !== 'createTime'
|
||||
)
|
||||
delete form[item] //清除多余参数(已经放在config中)
|
||||
})
|
||||
form.config = config
|
||||
|
||||
let bool = await FileConfigApi.updateFileConfig(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await FileConfigApi.deleteFileConfig(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 主配置按钮操作 */
|
||||
const handleMaster = async (id) => {
|
||||
try {
|
||||
await message.confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?')
|
||||
await FileConfigApi.updateFileConfigMaster(id)
|
||||
message.success(t('common.updateSuccess'))
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 测试按钮操作 */
|
||||
const handleTest = async (id) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await FileConfigApi.testFileConfig(id)
|
||||
message.alert(
|
||||
`<div>测试通过,上传文件成功!访问地址:</div>
|
||||
<div style="word-break: break-word;">${response}</div>
|
||||
`,
|
||||
'',
|
||||
{
|
||||
dangerouslyUseHTMLString: true
|
||||
}
|
||||
)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
398
src/views/infra/job/index.vue
Normal file
398
src/views/infra/job/index.vue
Normal file
@@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
v-model="tableForm"
|
||||
ref="crudRef"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:job:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
<el-button type="info" plain @click="handleJobLog()" v-hasPermi="['infra:job:query']">
|
||||
<Icon icon="ep:zoom-in" class="mr-5px" /> 执行日志
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
class="is-text"
|
||||
:icon="
|
||||
scope.row.status === InfraJobStatusEnum.STOP
|
||||
? 'el-icon-video-play'
|
||||
: 'el-icon-video-pause'
|
||||
"
|
||||
@click="handleChangeStatus(scope.row)"
|
||||
v-hasPermi="['infra:job:update']"
|
||||
>
|
||||
{{ scope.row.status === InfraJobStatusEnum.STOP ? '开启' : '暂停' }}
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
@command="(command) => handleCommand(command, scope.row)"
|
||||
v-hasPermi="['infra:job:trigger', 'infra:job:query']"
|
||||
>
|
||||
<div class="pt-3px pr-4px pb-3px pl-4px cursor-pointer">
|
||||
<el-text type="primary">
|
||||
<span>更多</span>
|
||||
<Icon :size="16" icon="iconamoon:arrow-down-2-light" />
|
||||
</el-text>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="handleRun" v-if="checkPermi(['infra:job:trigger'])">
|
||||
执行一次
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click="crudRef.rowView(scope.row, scope.index)"
|
||||
v-if="checkPermi(['infra:job:query'])"
|
||||
>
|
||||
任务详细
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="handleJobLog" v-if="checkPermi(['infra:job:query'])">
|
||||
调度日志
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<template #accountCount="scope">
|
||||
<el-tag>{{ scope.row.accountCount }}</el-tag>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.INFRA_JOB_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<template #cronExpression-form="{ type }">
|
||||
<div v-if="type == 'view'">{{ tableForm.cronExpression }}</div>
|
||||
<Crontab v-else v-model="tableForm.cronExpression" />
|
||||
</template>
|
||||
<template #stayus-form="{ value }">
|
||||
<dict-tag v-if="value" :type="DICT_TYPE.INFRA_JOB_STATUS" :value="value" />
|
||||
</template>
|
||||
<template #executionTime-form>
|
||||
<div class="pt-10px">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(nextTime, index) in nextTimes"
|
||||
:key="index"
|
||||
:timestamp="formatDate(nextTime)"
|
||||
>
|
||||
第 {{ index + 1 }} 次
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<DesignPopup v-model="logPopup.show" :title="logPopup.title" width="80%" controlType="drawer">
|
||||
<div class="p-20px">
|
||||
<InfraJobLog ref="logRef" :jobId="logPopup.jobId"></InfraJobLog>
|
||||
</div>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as JobApi from '@/api/infra/job'
|
||||
import { InfraJobStatusEnum } from '@/utils/constants'
|
||||
import InfraJobLog from './logger/index.vue'
|
||||
|
||||
defineOptions({ name: 'SystemTenant' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
const nextTimes = ref([]) // 下一轮执行时间的数组
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
searchLabelWidth: 120,
|
||||
menuWidth: 300,
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 120,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
name: {
|
||||
label: '任务名称',
|
||||
minWidth: 110,
|
||||
search: true,
|
||||
rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '任务状态',
|
||||
width: 90,
|
||||
search: true,
|
||||
type: 'select',
|
||||
display: false,
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_JOB_STATUS)
|
||||
},
|
||||
handlerName: {
|
||||
label: '处理器的名字',
|
||||
minWidth: 100,
|
||||
search: true,
|
||||
editDisabled: true,
|
||||
rules: [{ required: true, message: '处理器的名字不能为空', trigger: 'blur' }]
|
||||
},
|
||||
handlerParam: {
|
||||
label: '处理器的参数',
|
||||
minWidth: 110
|
||||
},
|
||||
cronExpression: {
|
||||
label: 'CRON 表达式',
|
||||
minWidth: 110,
|
||||
rules: [{ required: true, message: 'CRON 表达式不能为空', trigger: 'blur' }]
|
||||
},
|
||||
retryCount: {
|
||||
label: '重试次数',
|
||||
hide: true,
|
||||
rules: [{ required: true, message: '重试次数不能为空', trigger: 'blur' }],
|
||||
placeholder: '请输入重试次数。设置为 0 时,不进行重试'
|
||||
},
|
||||
retryInterval: {
|
||||
label: '重试间隔',
|
||||
hide: true,
|
||||
rules: [{ required: true, message: '重试间隔不能为空', trigger: 'blur' }],
|
||||
placeholder: '请输入重试间隔,单位:毫秒。设置为 0 时,无需间隔'
|
||||
},
|
||||
monitorTimeout: {
|
||||
label: '监控超时时间',
|
||||
placeholder: '请输入监控超时时间,单位:毫秒',
|
||||
hide: true
|
||||
},
|
||||
executionTime: {
|
||||
label: '后续执行时间',
|
||||
display: false,
|
||||
hide: true
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const logPopup = ref({ show: false, title: '', jobId: 0 })
|
||||
const permission = getCurrPermi(['infra:job'])
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
const crudRef = ref()
|
||||
const logRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await JobApi.getJobPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
loading.value = true
|
||||
tableForm.value = await JobApi.getJob(tableForm.value.id)
|
||||
if (type == 'view') nextTimes.value = await JobApi.getJobNextTimes(tableForm.value.id!)
|
||||
loading.value = false
|
||||
}
|
||||
if (type === 'view') {
|
||||
tableOption.column.status.display = true
|
||||
tableOption.column.executionTime.display = true
|
||||
} else {
|
||||
tableOption.column.status.display = false
|
||||
tableOption.column.executionTime.display = false
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await JobApi.createJob(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await JobApi.updateJob(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await JobApi.deleteJob(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 修改状态操作 */
|
||||
const handleChangeStatus = async (row: JobApi.JobVO) => {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const text = row.status === InfraJobStatusEnum.STOP ? '开启' : '关闭'
|
||||
await message.confirm(
|
||||
'确认要' + text + '定时任务编号为"' + row.id + '"的数据项?',
|
||||
t('common.reminder')
|
||||
)
|
||||
const status =
|
||||
row.status === InfraJobStatusEnum.STOP ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP
|
||||
await JobApi.updateJobStatus(row.id, status)
|
||||
message.success(text + '成功')
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {
|
||||
// 取消后,进行恢复按钮
|
||||
row.status =
|
||||
row.status === InfraJobStatusEnum.NORMAL ? InfraJobStatusEnum.STOP : InfraJobStatusEnum.NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
/** '更多'操作按钮 */
|
||||
const handleCommand = (command, row) => {
|
||||
switch (command) {
|
||||
case 'handleRun':
|
||||
handleRun(row)
|
||||
break
|
||||
case 'handleJobLog':
|
||||
handleJobLog(row)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 执行一次 */
|
||||
const handleRun = async (row: JobApi.JobVO) => {
|
||||
try {
|
||||
// 二次确认
|
||||
await message.confirm('确认要立即执行一次' + row.name + '?', t('common.reminder'))
|
||||
// 提交执行
|
||||
await JobApi.runJob(row.id)
|
||||
message.success('执行成功')
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 跳转执行日志 */
|
||||
const handleJobLog = (row?) => {
|
||||
logPopup.value = {
|
||||
show: true,
|
||||
title: row ? `${row.name} 调度日志` : '所有的调度日志',
|
||||
jobId: row?.id || 0
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (logRef.value) logRef.value.resetChange()
|
||||
}, 30)
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await JobApi.exportJob(searchObj)
|
||||
download.excel(data, '定时任务列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-dropdown {
|
||||
padding: 4px 2px;
|
||||
}
|
||||
</style>
|
||||
230
src/views/infra/job/logger/index.vue
Normal file
230
src/views/infra/job/logger/index.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:tenant:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.INFRA_JOB_LOG_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<template #beginTimeText-form>
|
||||
{{ formatDate(tableForm.beginTime) + ' ~ ' + formatDate(tableForm.endTime) }}
|
||||
</template>
|
||||
<template #duration-form="{ value }">
|
||||
{{ value + ' 毫秒' }}
|
||||
</template>
|
||||
<template #status-form="{ value }">
|
||||
<dict-tag
|
||||
v-if="value !== undefined"
|
||||
:type="DICT_TYPE.INFRA_JOB_LOG_STATUS"
|
||||
:value="value"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDate, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as JobLogApi from '@/api/infra/jobLog'
|
||||
|
||||
defineOptions({ name: 'InfraJobLog' })
|
||||
|
||||
interface Props {
|
||||
jobId?: number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
searchLabelWidth: 100,
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 120,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
jobId: {
|
||||
label: '任务编号',
|
||||
width: 90
|
||||
},
|
||||
handlerName: {
|
||||
label: '处理器的名字',
|
||||
search: true,
|
||||
searchSpan: 5
|
||||
},
|
||||
beginTime: {
|
||||
label: '执行时间',
|
||||
hide: true,
|
||||
display: false,
|
||||
search: true,
|
||||
type: 'date',
|
||||
searchRange: true,
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始执行时间',
|
||||
endPlaceholder: '结束执行时间',
|
||||
searchSpan: 8,
|
||||
},
|
||||
handlerParam: {
|
||||
label: '处理器的参数'
|
||||
},
|
||||
executeIndex: {
|
||||
label: '第几次执行',
|
||||
width: 100
|
||||
},
|
||||
beginTimeText: {
|
||||
label: '执行时间',
|
||||
html: true,
|
||||
width: 160,
|
||||
formatter: (row) => {
|
||||
return `<div>
|
||||
<div>${formatDate(row.beginTime)}</div>
|
||||
<div>${formatDate(row.endTime)}</div>
|
||||
</div>`
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
label: '执行时长',
|
||||
width: 100,
|
||||
formatter: (row) => {
|
||||
return row.duration + '毫秒'
|
||||
}
|
||||
},
|
||||
status: {
|
||||
label: '任务状态',
|
||||
searchSpan: 5,
|
||||
search: true,
|
||||
width: 90,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_JOB_LOG_STATUS)
|
||||
},
|
||||
result: {
|
||||
label: '执行结果',
|
||||
hide: true
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj: any = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
if (searchObj.beginTime?.length) {
|
||||
const dateArr = getSearchDate(searchObj.beginTime)
|
||||
searchObj.beginTime = dateArr[0]
|
||||
searchObj.endTime = dateArr[1]
|
||||
} else delete searchObj.beginTime
|
||||
if (props.jobId !== 0) searchObj['jobId'] = props.jobId
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await JobLogApi.getJobLogPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj: any = { ...tableSearch.value }
|
||||
if (props.jobId) searchObj.jobId = props.jobId
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
|
||||
const data = await JobLogApi.exportJobLog(searchObj)
|
||||
download.excel(data, '调度日志列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
|
||||
defineExpose({ resetChange })
|
||||
</script>
|
||||
265
src/views/infra/redis/index.vue
Normal file
265
src/views/infra/redis/index.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
|
||||
<el-row>
|
||||
<!-- 基本信息 -->
|
||||
<el-col :span="24" class="card-box" shadow="hover">
|
||||
<el-card>
|
||||
<el-descriptions title="基本信息" :column="6" border>
|
||||
<el-descriptions-item label="Redis版本 :">
|
||||
{{ cache?.info?.redis_version }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运行模式 :">
|
||||
{{ cache?.info?.redis_mode == 'standalone' ? '单机' : '集群' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="端口 :">
|
||||
{{ cache?.info?.tcp_port }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="客户端数 :">
|
||||
{{ cache?.info?.connected_clients }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运行时间(天) :">
|
||||
{{ cache?.info?.uptime_in_days }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="使用内存 :">
|
||||
{{ cache?.info?.used_memory_human }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="使用CPU :">
|
||||
{{ cache?.info ? parseFloat(cache?.info?.used_cpu_user_children).toFixed(2) : '' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="内存配置 :">
|
||||
{{ cache?.info?.maxmemory_human }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="AOF是否开启 :">
|
||||
{{ cache?.info?.aof_enabled == '0' ? '否' : '是' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="RDB是否成功 :">
|
||||
{{ cache?.info?.rdb_last_bgsave_status }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="Key数量 :">
|
||||
{{ cache?.dbSize }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="网络入口/出口 :">
|
||||
{{ cache?.info?.instantaneous_input_kbps }}kps/
|
||||
{{ cache?.info?.instantaneous_output_kbps }}kps
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 命令统计 -->
|
||||
<el-col :span="12" class="mt-3">
|
||||
<el-card :gutter="12" shadow="hover">
|
||||
<Echart :options="commandStatsRefChika" :height="420" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 内存使用量统计 -->
|
||||
<el-col :span="12" class="mt-3">
|
||||
<el-card class="ml-3" :gutter="12" shadow="hover">
|
||||
<Echart :options="usedmemoryEchartChika" :height="420" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as RedisApi from '@/api/infra/redis'
|
||||
import { RedisMonitorInfoVO } from '@/api/infra/redis/types'
|
||||
const cache = ref<RedisMonitorInfoVO>()
|
||||
|
||||
// 基本信息
|
||||
const readRedisInfo = async () => {
|
||||
const data = await RedisApi.getCache()
|
||||
cache.value = data
|
||||
}
|
||||
|
||||
// 内存使用情况
|
||||
const usedmemoryEchartChika = reactive<any>({
|
||||
title: {
|
||||
// 仪表盘标题。
|
||||
text: '内存使用情况',
|
||||
left: 'center',
|
||||
show: true, // 是否显示标题,默认 true。
|
||||
offsetCenter: [0, '20%'], //相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
|
||||
color: 'yellow', // 文字的颜色,默认 #333。
|
||||
fontSize: 20 // 文字的字体大小,默认 15。
|
||||
},
|
||||
toolbox: {
|
||||
show: false,
|
||||
feature: {
|
||||
restore: { show: true },
|
||||
saveAsImage: { show: true }
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '峰值',
|
||||
type: 'gauge',
|
||||
min: 0,
|
||||
max: 50,
|
||||
splitNumber: 10,
|
||||
//这是指针的颜色
|
||||
color: '#F5C74E',
|
||||
radius: '85%',
|
||||
center: ['50%', '50%'],
|
||||
startAngle: 225,
|
||||
endAngle: -45,
|
||||
axisLine: {
|
||||
// 坐标轴线
|
||||
lineStyle: {
|
||||
// 属性lineStyle控制线条样式
|
||||
color: [
|
||||
[0.2, '#7FFF00'],
|
||||
[0.8, '#00FFFF'],
|
||||
[1, '#FF0000']
|
||||
],
|
||||
//width: 6 外框的大小(环的宽度)
|
||||
width: 10
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
// 坐标轴小标记
|
||||
//里面的线长是5(短线)
|
||||
length: 5, // 属性length控制线长
|
||||
lineStyle: {
|
||||
// 属性lineStyle控制线条样式
|
||||
color: '#76D9D7'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
// 分隔线
|
||||
length: 20, // 属性length控制线长
|
||||
lineStyle: {
|
||||
// 属性lineStyle(详见lineStyle)控制线条样式
|
||||
color: '#76D9D7'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#76D9D7',
|
||||
distance: 15,
|
||||
fontSize: 15
|
||||
},
|
||||
pointer: {
|
||||
// 指针的大小
|
||||
width: 7,
|
||||
show: true
|
||||
},
|
||||
detail: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
// 里面文字下的数值大小(50)
|
||||
fontSize: 15,
|
||||
color: '#FFFFFF'
|
||||
},
|
||||
valueAnimation: true
|
||||
},
|
||||
progress: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 指令使用情况
|
||||
const commandStatsRefChika = reactive({
|
||||
title: {
|
||||
text: '命令统计',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
orient: 'vertical',
|
||||
right: 30,
|
||||
top: 10,
|
||||
bottom: 20,
|
||||
data: [] as any[],
|
||||
textStyle: {
|
||||
color: '#a1a1a1'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '命令',
|
||||
type: 'pie',
|
||||
radius: [20, 120],
|
||||
center: ['40%', '60%'],
|
||||
data: [] as any[],
|
||||
roseType: 'radius',
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
/** 加载数据 */
|
||||
const getSummary = () => {
|
||||
// 初始化命令图表
|
||||
initCommandStatsChart()
|
||||
usedMemoryInstance()
|
||||
}
|
||||
|
||||
/** 命令使用情况 */
|
||||
const initCommandStatsChart = async () => {
|
||||
usedmemoryEchartChika.series[0].data = []
|
||||
// 发起请求
|
||||
try {
|
||||
const data = await RedisApi.getCache()
|
||||
cache.value = data
|
||||
// 处理数据
|
||||
const commandStats = [] as any[]
|
||||
const nameList = [] as string[]
|
||||
data.commandStats.forEach((row) => {
|
||||
commandStats.push({
|
||||
name: row.command,
|
||||
value: row.calls
|
||||
})
|
||||
nameList.push(row.command)
|
||||
})
|
||||
commandStatsRefChika.legend.data = nameList
|
||||
commandStatsRefChika.series[0].data = commandStats
|
||||
} catch {}
|
||||
}
|
||||
const usedMemoryInstance = async () => {
|
||||
try {
|
||||
const data = await RedisApi.getCache()
|
||||
cache.value = data
|
||||
// 仪表盘详情,用于显示数据。
|
||||
usedmemoryEchartChika.series[0].detail = {
|
||||
show: true, // 是否显示详情,默认 true。
|
||||
offsetCenter: [0, '50%'], // 相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
|
||||
color: 'auto', // 文字的颜色,默认 auto。
|
||||
fontSize: 30, // 文字的字体大小,默认 15。
|
||||
formatter: cache.value!.info.used_memory_human // 格式化函数或者字符串
|
||||
}
|
||||
|
||||
usedmemoryEchartChika.series[0].data[0] = {
|
||||
value: cache.value!.info.used_memory_human,
|
||||
name: '内存消耗'
|
||||
}
|
||||
usedmemoryEchartChika.tooltip = {
|
||||
formatter: '{b} <br/>{a} : ' + cache.value!.info.used_memory_human
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
// 读取 redis 信息
|
||||
readRedisInfo()
|
||||
// 加载数据
|
||||
getSummary()
|
||||
})
|
||||
</script>
|
||||
27
src/views/infra/server/index.vue
Normal file
27
src/views/infra/server/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraAdminServer' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/admin/applications')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 友情提示:如果访问出现 404 问题:
|
||||
const data = await ConfigApi.getConfigKey('url.spring-boot-admin')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
25
src/views/infra/skywalking/index.vue
Normal file
25
src/views/infra/skywalking/index.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraSkyWalking' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref('')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.skywalking')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
26
src/views/infra/swagger/index.vue
Normal file
26
src/views/infra/swagger/index.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
defineOptions({ name: 'InfraSwagger' })
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/doc.html') // Knife4j UI
|
||||
// const src = ref(import.meta.env.VITE_BASE_URL + '/swagger-ui') // Swagger UI
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.swagger')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
184
src/views/infra/webSocket/index.vue
Normal file
184
src/views/infra/webSocket/index.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<!-- 左侧:建立连接、发送消息 -->
|
||||
<el-card :gutter="12" class="w-1/2" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>连接</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-4 text-lg font-medium"> 连接状态: </span>
|
||||
<el-tag :color="getTagColor">{{ status }}</el-tag>
|
||||
</div>
|
||||
<hr class="my-4" />
|
||||
<div class="flex">
|
||||
<el-input v-model="server" disabled>
|
||||
<template #prepend>服务地址</template>
|
||||
</el-input>
|
||||
<el-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggleConnectStatus">
|
||||
{{ getIsOpen ? '关闭连接' : '开启连接' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<p class="mt-4 text-lg font-medium">消息输入框</p>
|
||||
<hr class="my-4" />
|
||||
<el-input
|
||||
v-model="sendText"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
:disabled="!getIsOpen"
|
||||
clearable
|
||||
placeholder="请输入你要发送的消息"
|
||||
type="textarea"
|
||||
/>
|
||||
<el-select v-model="sendUserId" class="mt-4" placeholder="请选择发送人">
|
||||
<el-option key="" label="所有人" value="" />
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button :disabled="!getIsOpen" block class="ml-2 mt-4" type="primary" @click="handlerSend">
|
||||
发送
|
||||
</el-button>
|
||||
</el-card>
|
||||
<!-- 右侧:消息记录 -->
|
||||
<el-card :gutter="12" class="w-1/2" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>消息记录</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="max-h-80 overflow-auto">
|
||||
<ul>
|
||||
<li v-for="msg in messageReverseList" :key="msg.time" class="mt-2">
|
||||
<div class="flex items-center">
|
||||
<span class="text-primary mr-2 font-medium">收到消息:</span>
|
||||
<span>{{ formatDate(new Date(msg.time)) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ msg.text }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { useWebSocket } from '@vueuse/core'
|
||||
import { getRefreshToken } from '@/utils/auth'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'InfraWebSocket' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const server = ref(
|
||||
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') +
|
||||
'?token=' +
|
||||
getRefreshToken() // 使用 getRefreshToken() 方法,而不使用 getAccessToken() 方法的原因:WebSocket 无法方便的刷新访问令牌
|
||||
) // WebSocket 服务地址
|
||||
const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开
|
||||
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色
|
||||
|
||||
/** 发起 WebSocket 连接 */
|
||||
const { status, data, send, close, open } = useWebSocket(server.value, {
|
||||
autoReconnect: true,
|
||||
heartbeat: true
|
||||
})
|
||||
|
||||
/** 监听接收到的数据 */
|
||||
const messageList = ref([] as { time: number; text: string }[]) // 消息列表
|
||||
const messageReverseList = computed(() => messageList.value.slice().reverse())
|
||||
watchEffect(() => {
|
||||
if (!data.value) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 1. 收到心跳
|
||||
if (data.value === 'pong') {
|
||||
// state.recordList.push({
|
||||
// text: '【心跳】',
|
||||
// time: new Date().getTime()
|
||||
// })
|
||||
return
|
||||
}
|
||||
|
||||
// 2.1 解析 type 消息类型
|
||||
const jsonMessage = JSON.parse(data.value)
|
||||
const type = jsonMessage.type
|
||||
const content = JSON.parse(jsonMessage.content)
|
||||
if (!type) {
|
||||
message.error('未知的消息类型:' + data.value)
|
||||
return
|
||||
}
|
||||
// 2.2 消息类型:demo-message-receive
|
||||
if (type === 'demo-message-receive') {
|
||||
const single = content.single
|
||||
if (single) {
|
||||
messageList.value.push({
|
||||
text: `【单发】用户编号(${content.fromUserId}):${content.text}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
} else {
|
||||
messageList.value.push({
|
||||
text: `【群发】用户编号(${content.fromUserId}):${content.text}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
// 2.3 消息类型:notice-push
|
||||
if (type === 'notice-push') {
|
||||
messageList.value.push({
|
||||
text: `【系统通知】:${content.title}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
return
|
||||
}
|
||||
message.error('未处理消息:' + data.value)
|
||||
} catch (error) {
|
||||
message.error('处理消息发生异常:' + data.value)
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
|
||||
/** 发送消息 */
|
||||
const sendText = ref('') // 发送内容
|
||||
const sendUserId = ref('') // 发送人
|
||||
const handlerSend = () => {
|
||||
// 1.1 先 JSON 化 message 消息内容
|
||||
const messageContent = JSON.stringify({
|
||||
text: sendText.value,
|
||||
toUserId: sendUserId.value
|
||||
})
|
||||
// 1.2 再 JSON 化整个消息
|
||||
const jsonMessage = JSON.stringify({
|
||||
type: 'demo-message-send',
|
||||
content: messageContent
|
||||
})
|
||||
// 2. 最后发送消息
|
||||
send(jsonMessage)
|
||||
sendText.value = ''
|
||||
}
|
||||
|
||||
/** 切换 websocket 连接状态 */
|
||||
const toggleConnectStatus = () => {
|
||||
if (getIsOpen.value) {
|
||||
close()
|
||||
} else {
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
// 获取用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
|
||||
</script>
|
||||
274
src/views/lowTemplate/generalPage/album/albumForm.vue
Normal file
274
src/views/lowTemplate/generalPage/album/albumForm.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<div class="w-100%">
|
||||
<div class="xc_box">
|
||||
<FormView
|
||||
:ref="(el) => (formViewRef[formTopData.refKey] = el)"
|
||||
formType="add"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="formTopData.defaultData"
|
||||
:showButton="false"
|
||||
:enhanceData="{isShowFun}"
|
||||
:formId="formTopData.formId"
|
||||
></FormView>
|
||||
<div class="Tips">
|
||||
<div style="display: flex;align-items: center;" v-if="is_show">
|
||||
<span class="span"></span>
|
||||
<span>图片列表</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-checkbox v-model="checked" label="全选" size="large" style="margin-right:10px;font-size: 16px;" @click="toggleAllSelection()"/>
|
||||
<el-button @click="uploadFun(true)">移动图片</el-button>
|
||||
<el-button @click="delFun()">删除图片</el-button>
|
||||
<el-button @click="isShowFun()">退出操作</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-select v-model="value" placeholder="Select" style="width: 120px">
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<LowTable :ref="(el)=>(ref1 = el)" :tableId="tableDate.tableId" :enhanceData="tableDate.enhanceData" > </LowTable>
|
||||
</div>
|
||||
<el-dialog v-model="upload" title="选择相册" width="728px" :close-on-click-modal="false">
|
||||
<el-checkbox-group v-model="checkList">
|
||||
<div class="checkbox-box">
|
||||
<div class="list" v-for="(item,index) in listData" :key="index">
|
||||
<img class="img" :src="item.url" alt=""/>
|
||||
<div class="radio">
|
||||
<el-checkbox label="" :value="index" />
|
||||
<p>{{item.title}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
<div class="btn">
|
||||
<el-button class="qx" @click="uploadFun(false)">取消</el-button>
|
||||
<el-button class="bc" type="primary" @click="uploadFun(false)">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { messageConfig ,ElMessage, ElMessageBox} from 'element-plus';
|
||||
|
||||
const ref1 = ref()
|
||||
const formViewRef = ref({})
|
||||
const value = ref('1')
|
||||
const upload = ref(false)
|
||||
const radio = ref('selected and disabled')
|
||||
|
||||
const formTopData = ref({
|
||||
type:'table',
|
||||
refKey: 'xcgl',
|
||||
formId: '1852223511034294274',
|
||||
defaultData: {},
|
||||
enhanceData: { hideHeader: 'disabled'}
|
||||
})
|
||||
|
||||
|
||||
const tableDate = ref({
|
||||
label:'图片列表',
|
||||
name:'fourth',
|
||||
defaultData:{},
|
||||
tableId:'1852241979997646849',
|
||||
type:'table',
|
||||
enhanceData: { hideHeader: 'disabled',}
|
||||
})
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: '1',
|
||||
label: '文件名称',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '文件大小',
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: '更新时间',
|
||||
},
|
||||
]
|
||||
|
||||
const checked = ref()
|
||||
const is_show = ref(true)
|
||||
const isShowFun = () =>{
|
||||
ref1.value.crudRef.tableOption.selection=is_show.value
|
||||
is_show.value = !is_show.value
|
||||
checked.value = !is_show.value ? '' : true
|
||||
ref1.value.crudRef.refreshTable()
|
||||
|
||||
}
|
||||
|
||||
//全选
|
||||
const toggleAllSelection = ()=> {
|
||||
ref1.value.crudRef.toggleAllSelection()
|
||||
}
|
||||
//删除图片
|
||||
const delFun = () =>{
|
||||
ElMessageBox.confirm(
|
||||
'是否确定删除数据?',
|
||||
'操作确认',
|
||||
{
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '数据删除成功',
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
//上传图片
|
||||
const checkList = ref([])
|
||||
const listData = ref([
|
||||
{title:'相册名称',url:'../../../../../public/img/xc.png'},
|
||||
{title:'相册名称',url:'../../../../../public/img/xc.png'},
|
||||
{title:'相册名称',url:'../../../../../public/img/xc.png'},
|
||||
{title:'相册名称',url:'../../../../../public/img/xc.png'},
|
||||
{title:'相册名称',url:'../../../../../public/img/xc.png'},
|
||||
{title:'相册名称',url:'../../../../../public/img/xc.png'},
|
||||
{title:'相册名称',url:'../../../../../public/img/xc.png'},
|
||||
{title:'相册名称',url:'../../../../../public/img/xc.png'},
|
||||
])
|
||||
const uploadFun = (type) =>{
|
||||
upload.value = type
|
||||
}
|
||||
|
||||
|
||||
const xzFun = ()=>{
|
||||
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xc_box{
|
||||
padding-bottom: 20px;
|
||||
|
||||
.Tips{
|
||||
display: flex;
|
||||
height: 40px;
|
||||
padding-right:18px;
|
||||
margin-bottom: 15px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.span{
|
||||
display: inline-block;
|
||||
width: 8.41px;
|
||||
height: 19px;
|
||||
margin-right:10px;
|
||||
background: #409eff;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-box{
|
||||
display: flex;
|
||||
padding: 0 0 30px 30px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.list{
|
||||
width: 150px;
|
||||
height: 180px;
|
||||
margin: 20px 20px 0 0;
|
||||
border: 1px solid #e4e4e4;
|
||||
|
||||
.img{
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.radio{
|
||||
display: flex;
|
||||
height: 30px;
|
||||
padding-left: 10px;
|
||||
// margin-left:5px;
|
||||
line-height: 30px;
|
||||
|
||||
// .el-checkbox{
|
||||
.el-checkbox__input{
|
||||
top: -5px;
|
||||
left: 6px;
|
||||
height: 30px;
|
||||
margin: 0;
|
||||
}
|
||||
// }
|
||||
|
||||
|
||||
p{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-left: 5px;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 30px;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn{
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
padding: 20px 30px;
|
||||
border-top:1px solid #e9e9e9;
|
||||
|
||||
.el-button{
|
||||
width: 80px;
|
||||
height: 30px;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.qx{
|
||||
color: #999;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.qx:hover{
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.qr{
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-select__placeholder{
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
274
src/views/lowTemplate/generalPage/feature/feature_grzx1.vue
Normal file
274
src/views/lowTemplate/generalPage/feature/feature_grzx1.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<div class="grzx_box w-95% bg-white flex">
|
||||
<el-menu
|
||||
:default-active="mentDefaultActive"
|
||||
class="pt-25px el-menu-vertical-demo"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<template v-for="item in menuList" :key="item.id">
|
||||
<el-menu-item class="flex justify-center" :index="item.index">
|
||||
<span> {{ item.menuItemTitle }}</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-menu>
|
||||
|
||||
<div class="use-lowdesign">
|
||||
<template v-if="richFormView.defaultData.richText">
|
||||
<FormView
|
||||
formType="view"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="richFormView.defaultData"
|
||||
:enhanceData="richFormView.enhanceData"
|
||||
:formId="richFormView.formId"
|
||||
></FormView>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<template v-if="menuContent.type == 'exploit'">
|
||||
<div class="content p-40px">
|
||||
<div class="title text-18px pl-7px mb-20px fw600 c-#666666">
|
||||
{{ menuContent.title }}
|
||||
</div>
|
||||
<FormView
|
||||
formType="edit"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="menuContent.defaultData"
|
||||
:formId="menuContent.formId"
|
||||
></FormView>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="menuContent.type == 'design'">
|
||||
<div class="content p-40px">
|
||||
<div>
|
||||
<div class="title text-18px pl-7px mb-20px fw600 c-#666666">
|
||||
{{ menuContent.title }}
|
||||
</div>
|
||||
<div class="xttz-util flex" v-if="menuContent.tableId == '1840642756645044226'">
|
||||
<div v-for="item in xttzBtnList" :key="item.id">
|
||||
<el-button
|
||||
:class="xttzActiveClass == item.id ? 'is-active' : ''"
|
||||
@click="xttzBtnClick(item.id)"
|
||||
>{{ item.title }}</el-button
|
||||
>
|
||||
</div>
|
||||
<div class="all-read">
|
||||
<el-button>全部标记为已读</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<LowTable
|
||||
ref="xttzTable"
|
||||
:tableId="menuContent.tableId"
|
||||
:enhanceData="menuContent.defaultData"
|
||||
:fixed-search="xttzActiveClass != 1 ? { type: xttzActiveClass } : {}"
|
||||
>
|
||||
</LowTable>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { FormView, LowTable } from '@/components/LowDesign/index'
|
||||
|
||||
const menuList = ref([
|
||||
{ id: 1, index: 'zlsz', menuItemTitle: '资料设置' },
|
||||
{ id: 2, index: 'xgmm', menuItemTitle: '修改密码' },
|
||||
{ id: 3, index: 'dljl', menuItemTitle: '登录记录' },
|
||||
{ id: 4, index: 'xttz', menuItemTitle: '系统通知' },
|
||||
{ id: 5, index: 'xxsz', menuItemTitle: '消息设置' }
|
||||
])
|
||||
|
||||
const xttzBtnList = ref([
|
||||
{ id: 1, title: '全部消息' },
|
||||
{ id: 2, title: '服务消息' },
|
||||
{ id: 3, title: '活动消息' },
|
||||
{ id: 4, title: '产品消息' },
|
||||
{ id: 5, title: '安全消息' },
|
||||
{ id: 6, title: '故障消息' }
|
||||
])
|
||||
const xttzActiveClass = ref(1)
|
||||
|
||||
const mentDefaultActive = ref('zlsz')
|
||||
|
||||
const menuContentList = ref({
|
||||
zlsz: {
|
||||
title: '基本资料',
|
||||
formId: '1840295435986018306',
|
||||
type: 'exploit',
|
||||
defaultData: {
|
||||
dlzh: 'Windir',
|
||||
updateImg: 'http://oss.yckxt.com/chatgpt/upload/1/2024-09-30/1/user.png',
|
||||
updateBtn: 'http://oss.yckxt.com/chatgpt/upload/1/2024-09-30/1/user.png'
|
||||
}
|
||||
},
|
||||
xgmm: {
|
||||
title: '修改密码',
|
||||
formId: '1840567733712330753',
|
||||
type: 'exploit'
|
||||
},
|
||||
dljl: {
|
||||
title: '登录记录',
|
||||
tableId: '1840577516125323266',
|
||||
type: 'design',
|
||||
defaultData: {
|
||||
type: 'view'
|
||||
}
|
||||
},
|
||||
xttz: {
|
||||
title: '系统通知',
|
||||
tableId: '1840642756645044226',
|
||||
type: 'design',
|
||||
defaultData: {
|
||||
type: 'view',
|
||||
rowClick(row) {
|
||||
if (row.rich_text) {
|
||||
richFormView.value.defaultData = {
|
||||
richText: row.rich_text,
|
||||
type: row.$type,
|
||||
title: row.title,
|
||||
createTime: row.create_time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
xxsz: {
|
||||
title: '消息设置',
|
||||
tableId: '1843476389638443010',
|
||||
type: 'design',
|
||||
defaultData: {
|
||||
type: 'view'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const xttzTable = ref()
|
||||
|
||||
interface RichFormViewDefault {
|
||||
createTime: string
|
||||
title?: string
|
||||
type?: string
|
||||
richText?: String
|
||||
}
|
||||
|
||||
const richFormView = ref({
|
||||
formId: '1840671420115873794',
|
||||
enhanceData: {
|
||||
richDisplay: false
|
||||
},
|
||||
defaultData: {
|
||||
type: '',
|
||||
title: '',
|
||||
createTime: '',
|
||||
richText: ''
|
||||
} as RichFormViewDefault
|
||||
})
|
||||
|
||||
watch(
|
||||
() => richFormView.value.enhanceData.richDisplay,
|
||||
(val) => {
|
||||
richFormView.value.defaultData.richText = ''
|
||||
}
|
||||
)
|
||||
|
||||
const menuContent = ref(menuContentList.value[mentDefaultActive.value])
|
||||
|
||||
const handleSelect = (key: string, keyPath: string[]) => {
|
||||
if (menuContent.value !== menuContentList.value[key] && richFormView.value.defaultData.richText) {
|
||||
richFormView.value.defaultData.richText = ''
|
||||
}
|
||||
|
||||
menuContent.value = menuContentList.value[key]
|
||||
}
|
||||
|
||||
const xttzBtnClick = (id: number) => {
|
||||
xttzActiveClass.value = id
|
||||
xttzTable.value.resetChange()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grzx_box {
|
||||
min-height: 800px;
|
||||
margin: 0 auto;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 4.71%);
|
||||
|
||||
.el-menu {
|
||||
width: 180px;
|
||||
min-height: 800px;
|
||||
flex-shrink: 0;
|
||||
background-color: #fcfcfc;
|
||||
border-radius: 5px 0 0 5px;
|
||||
|
||||
.el-menu-item {
|
||||
height: 45px;
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
font-weight: bold;
|
||||
background-color: rgb(64 158 255 / 9.8%);
|
||||
border-right: 3px solid rgb(64 158 255 / 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.use-lowdesign {
|
||||
width: calc(100% - 180px);
|
||||
|
||||
.content {
|
||||
width: calc(100% - 80px);
|
||||
height: calc(100% - 80px);
|
||||
|
||||
.title {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
line-height: 16px;
|
||||
border-left: 6px solid #409eff;
|
||||
}
|
||||
|
||||
.xttz-util {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.el-button {
|
||||
display: flex;
|
||||
width: 80px;
|
||||
height: 30px;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
|
||||
&.is-active {
|
||||
color: #409eff;
|
||||
background-color: white;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.all-read {
|
||||
margin-left: auto;
|
||||
|
||||
.el-button {
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(255 255 255 / 0%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
292
src/views/lowTemplate/generalPage/feature/feature_grzx2.vue
Normal file
292
src/views/lowTemplate/generalPage/feature/feature_grzx2.vue
Normal file
@@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<!-- <div> 个人中心</div> -->
|
||||
<div>
|
||||
<div class="box">
|
||||
<div class="box-left .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent ">
|
||||
<div class="box-left-top">
|
||||
<div class="head ">
|
||||
<div style="margin: 20px 0;">
|
||||
<el-avatar :size="60" :src="circleUrl" />
|
||||
</div>
|
||||
<p class="font color6 .dark:c-#fff!" style="margin-bottom:5px;font-size: 18px;">a123</p>
|
||||
<p class="fontFamily color9 .dark:c-#ccc!" style="font-size: 14px;">专注交互原型设计</p>
|
||||
</div>
|
||||
<div class="title-box">
|
||||
<el-row class="demo-avatar demo-basic">
|
||||
<el-col :span="8">
|
||||
<div class="content">
|
||||
<p class="font color3 .dark:c-#fff!" style="font-size: 16px;">1000</p>
|
||||
<p class="font-family color9 .dark:c-#ccc!" style="font-size: 12px;">文章</p>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="content" style="border-right: 1px solid #f4f4f4;border-left: 1px solid #f4f4f4;">
|
||||
<p class="font color3 .dark:c-#fff!" style="font-size: 16px;">1000</p>
|
||||
<p class="font-family color9 .dark:c-#ccc!" style="font-size: 12px;">应用</p>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="content">
|
||||
<p class="font color3 .dark:c-#fff!" style="font-size: 16px;">1000</p>
|
||||
<p class="font-family color9 .dark:c-#ccc!" style="font-size: 12px;">项目</p>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<p class="font color6 .dark:c-#fff!" style="margin:20px 0 10px 20px;font-size: 14px;">个人名片</p>
|
||||
<el-row>
|
||||
<div class="list-box">
|
||||
<el-col :span="2">
|
||||
<Icon icon="fa6-solid:user" width="18" height="18" style="color: #999" />
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<p class="fontFamily color6 .dark:c-#ccc!" style="font-size: 14px;">10年</p>
|
||||
</el-col>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="list-box">
|
||||
<el-col :span="2">
|
||||
<Icon icon="fa6-brands:qq" width="18" height="18" style="color: #999" />
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<p class="fontFamily color6 .dark:c-#ccc!" style="font-size: 14px;">20841030</p>
|
||||
</el-col>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="list-box">
|
||||
<el-col :span="2">
|
||||
<Icon icon="fa:weixin" width="18" height="18" style="color: #999" />
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<p class="fontFamily color6 .dark:c-#ccc!" style="font-size: 14px;">a123</p>
|
||||
</el-col>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="list-box">
|
||||
<el-col :span="2">
|
||||
<Icon icon="fa-brands:weibo" width="18" height="18" style="color: #999" />
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<p class="fontFamily color6 .dark:c-#ccc!" style="font-size: 14px;">淘宝创意部</p>
|
||||
</el-col>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="list" style="padding-bottom: 30px;">
|
||||
<p class="font color6 .dark:c-#fff!" style="margin:20px 0 10px 20px;font-size: 14px;">所属团队</p>
|
||||
<el-row v-for="(item,index) in sstdData" :key="index">
|
||||
<div class="list-sstd">
|
||||
<el-avatar :size="46" :src="item.src" />
|
||||
<div class="list-sstd-text">
|
||||
<p class="font color6 .dark:c-#fff!" style="font-size: 14px;">{{item.title}}</p>
|
||||
<p class="fontFamily color9 .dark:c-#ccc!" style="font-size: 12px;">{{item.content}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="list">
|
||||
<p class="font color6 .dark:c-#fff!" style="margin:20px 0 10px 20px;font-size: 14px;">标签</p>
|
||||
<div style="margin-left: 20px;">
|
||||
<el-badge class="item" v-for="(item, index) in data" :key="index">
|
||||
<el-button>{{ item.name }}</el-button>
|
||||
</el-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-right .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<template v-for="(item,index) in tableData" :key="index">
|
||||
<el-tab-pane :label="item.label" :name="item.name">
|
||||
<div class="tabs-box">
|
||||
<!-- <FormView
|
||||
|
||||
formType="add"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="item.defaultData"
|
||||
:showButton="true"
|
||||
:formId="item.formId"
|
||||
></FormView> -->
|
||||
<upDate v-if="item.type == 'jurisdiction'"></upDate>
|
||||
<template v-if="item.type == 'table'">
|
||||
<LowTable :tableId="item.tableId" :enhanceData="item.enhanceData" > </LowTable>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, toRefs,ref } from 'vue'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
|
||||
const state = reactive({
|
||||
circleUrl:
|
||||
'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
|
||||
})
|
||||
const data = ref([
|
||||
{name:'很有想法'},
|
||||
{name:'海纳百川'},
|
||||
{name:'川妹子'},
|
||||
{name:'专注设计'},
|
||||
{name:'喜欢吃辣'},
|
||||
{name:'好看'},
|
||||
{name:'能力强'},
|
||||
{name:'爱臭美'},
|
||||
])
|
||||
const sstdData = ref([
|
||||
{src:'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',title:'淘宝创意部',content:'设计呢,最重要的是开心'},
|
||||
{src:'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',title:'淘宝创意部',content:'设计呢,最重要的是开心'},
|
||||
{src:'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',title:'淘宝创意部',content:'设计呢,最重要的是开心'},
|
||||
])
|
||||
const tableData = reactive([
|
||||
{label:'应用',name:'first',defaultData:{},tableId:'1848631600935510017',type:'table',enhanceData: { hideHeader: 'disabled'}},
|
||||
{label:'项目',name:'second',defaultData:{},tableId:'1848637143087489025',type:'table',enhanceData: { hideHeader: 'disabled'}},
|
||||
{label:'文章',name:'fourth',defaultData:{},tableId:'1848649270435160066',type:'table',enhanceData: { hideHeader: 'disabled'}},
|
||||
])
|
||||
|
||||
const activeName = ref('first')
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
|
||||
const { circleUrl } = toRefs(state)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.box{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.box-left{
|
||||
width: 22.6%;
|
||||
max-height: 950px;
|
||||
// padding: 0 20px;
|
||||
margin-right:21px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
|
||||
.box-left-top{
|
||||
width: 100%;
|
||||
// padding: 0 20px;
|
||||
|
||||
.head{
|
||||
// margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title-box{
|
||||
margin:30px 0;
|
||||
|
||||
.content{
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.list{
|
||||
padding-bottom: 20px;
|
||||
border-top: 1px solid #ececec;
|
||||
|
||||
.list-box{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-left:20px;
|
||||
line-height: 28px;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.list-sstd{
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
margin-left:20px;
|
||||
|
||||
.list-sstd-text{
|
||||
margin-left: 10px;
|
||||
|
||||
p{
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.box-right{
|
||||
width: 77%;
|
||||
padding: 0 20px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-top: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
margin-top: 1.1rem;
|
||||
}
|
||||
|
||||
::v-deep .el-button{
|
||||
padding: 8px 15px !important;
|
||||
font-family: '微软雅黑', sans-serif !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 400 !important;
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
::v-deep .el-tabs__item{
|
||||
height: 60px !important;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important;
|
||||
font-size: 16px !important;
|
||||
font-weight: 700 !important;
|
||||
line-height: 30px !important;
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
.font{
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fontFamily{
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.font-family{
|
||||
font-family: MicrosoftYaHei, '微软雅黑', sans-serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.color3{
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.color6{
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.color9{
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
152
src/views/lowTemplate/generalPage/form/form_hxfb.vue
Normal file
152
src/views/lowTemplate/generalPage/form/form_hxfb.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<!-- <div> 发布表单页(横向分步) </div> -->
|
||||
<ContentWrap>
|
||||
<el-steps :active="active" align-center>
|
||||
<el-step v-for="(item, index) in stepList" :key="item.refKey">
|
||||
<template #icon>
|
||||
<div class="step-icon-box" :class="{ avtive: active == index }">
|
||||
<div v-if="active == index" class="mt--1px">{{ index + 1 }}</div>
|
||||
<div v-else class="mt-1px">
|
||||
<Icon :size="14" icon="ep:select" color="var(--el-color-primary)"></Icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="c-[var(--el-text-color-primary)]">{{ item.title }}</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<div
|
||||
class="c-[var(--el-color-info-light-3)] text-12px"
|
||||
:class="{ 'des-hide': active != index }"
|
||||
>{{ item.des }}</div
|
||||
>
|
||||
</template>
|
||||
</el-step>
|
||||
</el-steps>
|
||||
<template v-for="(item, index) in stepList" :key="item.refKey">
|
||||
<div v-if="item.formId" v-show="active == index">
|
||||
<FormView
|
||||
:ref="(el) => (formViewRef[item.refKey] = el)"
|
||||
formType="add"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:showButton="false"
|
||||
:formId="item.formId"
|
||||
></FormView>
|
||||
</div>
|
||||
<div v-if="active == index && active < 3" class="mt-40px flex justify-center">
|
||||
<el-button v-if="active != 0" @click="active = active - 1">上一步</el-button>
|
||||
<el-button type="primary" v-if="active <= 2" @click="setNextForm">{{
|
||||
active == 2 ? '提交' : '下一步'
|
||||
}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormView } from '@/components/LowDesign/index'
|
||||
defineOptions({ name: 'FormHxfb' })
|
||||
const active = ref(0)
|
||||
const stepList = ref([
|
||||
{
|
||||
refKey: 'basic_sp',
|
||||
title: '基本信息',
|
||||
des: '请填写公司基本信息',
|
||||
formId: '1829756210681274369'
|
||||
},
|
||||
{
|
||||
refKey: 'detail_sp',
|
||||
title: '详细信息',
|
||||
des: '请填写公司详细信息',
|
||||
formId: '1829756992465985538'
|
||||
},
|
||||
{
|
||||
refKey: 'financial_sp',
|
||||
title: '财务信息',
|
||||
des: '请填写公司财务信息',
|
||||
formId: '1829757052494864386'
|
||||
},
|
||||
{
|
||||
refKey: 'submit_sp',
|
||||
title: '提交成功',
|
||||
des: '公司信息提交成功'
|
||||
}
|
||||
])
|
||||
|
||||
const formViewRef = ref({})
|
||||
const formData = ref({})
|
||||
|
||||
const setNextForm = async () => {
|
||||
const currStep = stepList.value[active.value]
|
||||
if (currStep.formId) {
|
||||
const formRef = formViewRef.value[currStep.refKey].controlRef
|
||||
const data = await formRef.handleSubmit(true)
|
||||
formData.value[currStep.refKey] = data
|
||||
}
|
||||
active.value++
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep(.low-form) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.step-icon-box {
|
||||
display: flex;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: #fff;
|
||||
border: 2px solid var(--el-color-primary);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.avtive {
|
||||
color: #fff;
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-steps) {
|
||||
.el-step__head {
|
||||
.el-step__line {
|
||||
background-color: var(--el-color-info-light-7);
|
||||
}
|
||||
|
||||
.el-step__icon {
|
||||
width: 30px !important;
|
||||
}
|
||||
|
||||
&.is-finish {
|
||||
.el-step__line {
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-step:not(:last-child) {
|
||||
.el-step__head {
|
||||
&.is-process::before {
|
||||
position: absolute;
|
||||
top: calc(50% - 1px);
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
width: 50%;
|
||||
height: 2px;
|
||||
background-color: var(--el-color-primary);
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
201
src/views/lowTemplate/generalPage/form/form_zxfb.vue
Normal file
201
src/views/lowTemplate/generalPage/form/form_zxfb.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<!-- <div> 发布表单页(纵向分步) </div> -->
|
||||
<ContentWrap>
|
||||
<el-steps direction="vertical" :active="active">
|
||||
<el-step v-for="(item, index) in stepList" :key="item.refKey">
|
||||
<template #icon>
|
||||
<div class="step-icon-box" :class="{ avtive: active == index }">
|
||||
<div v-if="active == index" class="mt--1px">{{ index + 1 }}</div>
|
||||
<div v-else class="mt-1px">
|
||||
<Icon :size="14" icon="ep:select" color="var(--el-color-primary)"></Icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="c-[var(--el-text-color-primary)]">{{ item.title }}</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<div
|
||||
class="c-[var(--el-color-info-light-3)] text-12px"
|
||||
:class="{ 'des-hide': active != index }"
|
||||
>{{ item.des }}</div
|
||||
>
|
||||
<div v-if="item.formId" v-show="active == index">
|
||||
<FormView
|
||||
:ref="(el) => (formViewRef[item.refKey] = el)"
|
||||
formType="add"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:showButton="false"
|
||||
:formId="item.formId"
|
||||
></FormView>
|
||||
</div>
|
||||
<div v-if="active == index && active < 3" class="ml-65px mb-20px">
|
||||
<el-button v-if="active != 0" @click="active = active - 1">上一步</el-button>
|
||||
<el-button type="primary" v-if="active <= 2" @click="setNextForm">{{
|
||||
active == 2 ? '提交' : '下一步'
|
||||
}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-step>
|
||||
</el-steps>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormView } from '@/components/LowDesign/index'
|
||||
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
defineOptions({ name: 'FormZxfb' })
|
||||
|
||||
interface HandleClickProps {
|
||||
(formData: object): string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
addFun: HandleClickProps
|
||||
}>()
|
||||
|
||||
const active = ref(0)
|
||||
const stepList = ref([
|
||||
{
|
||||
refKey: 'basic',
|
||||
title: '基本信息',
|
||||
des: '请填写公司基本信息',
|
||||
formId: '1829756210681274369'
|
||||
},
|
||||
{
|
||||
refKey: 'detail',
|
||||
title: '详细信息',
|
||||
des: '请填写公司详细信息',
|
||||
formId: '1829756992465985538'
|
||||
},
|
||||
{
|
||||
refKey: 'financial',
|
||||
title: '财务信息',
|
||||
des: '请填写公司财务信息',
|
||||
formId: '1829757052494864386'
|
||||
},
|
||||
{
|
||||
refKey: 'submit',
|
||||
title: '提交成功',
|
||||
des: '公司信息提交成功'
|
||||
}
|
||||
])
|
||||
|
||||
const formViewRef = ref({})
|
||||
const formData = ref({})
|
||||
|
||||
const debounce = (func, wait) => {
|
||||
let timeout
|
||||
return function () {
|
||||
const args = arguments
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(() => func.apply(args), wait)
|
||||
}
|
||||
}
|
||||
|
||||
const setNextForm = debounce(async () => {
|
||||
const currStep = stepList.value[active.value]
|
||||
if (currStep.formId) {
|
||||
const formRef = formViewRef.value[currStep.refKey].controlRef
|
||||
const data = await formRef.handleSubmit(true)
|
||||
formData.value[currStep.refKey] = data
|
||||
|
||||
if (active.value == 2 && data) {
|
||||
interface primordialTableDataType {
|
||||
basic: {
|
||||
company_name: string
|
||||
client_state: string
|
||||
industry_type: string
|
||||
client_source: string
|
||||
client_level: string
|
||||
}
|
||||
}
|
||||
const userInfo = ref({} as ProfileVO)
|
||||
const users = await getUserProfile()
|
||||
userInfo.value = users
|
||||
let primordialTableData = {
|
||||
company_name: (formData.value as primordialTableDataType).basic.company_name,
|
||||
customer_status: (formData.value as primordialTableDataType).basic.client_state,
|
||||
industry_type: (formData.value as primordialTableDataType).basic.industry_type,
|
||||
customer_source: (formData.value as primordialTableDataType).basic.client_source,
|
||||
customer_star: (formData.value as primordialTableDataType).basic.client_level,
|
||||
add_people: userInfo.value.id,
|
||||
update_date: formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss'),
|
||||
bh: '20241001'
|
||||
}
|
||||
props.addFun(primordialTableData)
|
||||
}
|
||||
|
||||
active.value++
|
||||
}
|
||||
}, 500)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep(.low-form) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.step-icon-box {
|
||||
display: flex;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: #fff;
|
||||
border: 2px solid var(--el-color-primary);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.avtive {
|
||||
color: #fff;
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-steps) {
|
||||
.el-step__head {
|
||||
width: 30px;
|
||||
|
||||
.el-step__line {
|
||||
left: 14px;
|
||||
background-color: var(--el-color-info-light-7);
|
||||
}
|
||||
|
||||
.el-step__icon {
|
||||
width: 30px !important;
|
||||
}
|
||||
|
||||
&.is-finish {
|
||||
.el-step__line {
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-process::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 14px;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
width: 2px;
|
||||
height: 50%;
|
||||
background-color: var(--el-color-primary);
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
.des-hide {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
186
src/views/lowTemplate/generalPage/group/group_gjsxy.vue
Normal file
186
src/views/lowTemplate/generalPage/group/group_gjsxy.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<!-- <div> 高级筛选页 </div>
|
||||
顶部搜索:表单设计
|
||||
底部表格:表单开发 -->
|
||||
<div class="box">
|
||||
<div class="order-top">
|
||||
<FormView
|
||||
formType="edit"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="orderTopData.defaultData"
|
||||
:showButton="false"
|
||||
:enhanceData="{ setSearch}"
|
||||
:formId="orderTopData.formId"
|
||||
:ref="(el) => (tableRef[tableData.name] = el)"
|
||||
></FormView>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<span>已选条件:</span>
|
||||
<template v-for="tag in dynamicTags" :key="tag.label">
|
||||
<el-tag
|
||||
v-if="tag.show"
|
||||
closable
|
||||
:disable-transitions="false"
|
||||
@close="handleClose(tag)"
|
||||
>
|
||||
{{ tag.label }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<span class="qcsx" @click="delFun">清除筛选</span>
|
||||
</div>
|
||||
<div class="table">
|
||||
<LowTable :tableId="tableData.tableId" :enhanceData="tableData.enhanceData"> </LowTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormView, LowTable } from '@/components/LowDesign/index'
|
||||
import type { TagProps } from 'element-plus'
|
||||
|
||||
interface TagsItem {
|
||||
name: string
|
||||
type: TagProps['type']
|
||||
}
|
||||
|
||||
|
||||
// const dynamicTags = ref(['选择项一', '选择项一', '选择项一'])
|
||||
const dynamicTags = ref<any>([
|
||||
// { label: '选项一', type: 'primary',lx:'kh',show:true },
|
||||
// { label: '选项二', type: 'primary',lx:'kh',show:false },
|
||||
])
|
||||
|
||||
const orderTopData = ref({
|
||||
type: 'exploit',
|
||||
refKey: 'orderTop',
|
||||
formId: '1838814762506964994',
|
||||
defaultData: {
|
||||
type1:'1',
|
||||
type2:'1',
|
||||
type3:'1'
|
||||
}
|
||||
})
|
||||
|
||||
const tableRef = ref({})
|
||||
const formData = ref({})
|
||||
const tableData = ref(
|
||||
{
|
||||
label: '高级筛选',
|
||||
name: 'gjsx',
|
||||
tableId: '1838824808796180481',
|
||||
enhanceData: { hideHeader: 'disabled' }
|
||||
},
|
||||
)
|
||||
|
||||
const tableCrud = ref()
|
||||
const tableSearch = ref({})
|
||||
const label = ref<any>()
|
||||
|
||||
// 多选处理
|
||||
const khArr = ref<any>([])
|
||||
const hyArr = ref<any>([])
|
||||
const setSearch = (searchObj) => {
|
||||
|
||||
if(searchObj.type == 'kh'){
|
||||
searchObj.dic.map(item=>{
|
||||
item.type = 'primary'
|
||||
item.show = searchObj.value.includes(item.value) ? true : false
|
||||
item.lx = searchObj.type
|
||||
return item
|
||||
})
|
||||
khArr.value = searchObj.dic
|
||||
}
|
||||
if(searchObj.type == 'hy'){
|
||||
searchObj.dic.map(item=>{
|
||||
item.type = 'primary'
|
||||
item.show = searchObj.value.includes(item.value) ? true : false
|
||||
item.lx = searchObj.type
|
||||
return item
|
||||
})
|
||||
hyArr.value = searchObj.dic
|
||||
}
|
||||
strFnu(searchObj)
|
||||
}
|
||||
|
||||
const strFnu = (data) =>{
|
||||
let array = khArr.value.concat(hyArr.value)
|
||||
dynamicTags.value = array
|
||||
}
|
||||
|
||||
const delFun = () =>{
|
||||
dynamicTags.value = []
|
||||
}
|
||||
|
||||
//已选条件处理
|
||||
const str = ref<any>([])
|
||||
const handleClose = (tag) => {
|
||||
dynamicTags.value.map(item =>{
|
||||
if(item.label == tag.label){
|
||||
item.show = false
|
||||
}
|
||||
|
||||
if(item.lx == tag.lx && item.show){
|
||||
str.value.push(item.value)
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
|
||||
if(tag.lx == 'kh'){
|
||||
orderTopData.value.defaultData.type1 = str.value.join()
|
||||
str.value = []
|
||||
} else if(tag.lx == 'hy'){
|
||||
orderTopData.value.defaultData.type2 = str.value.join()
|
||||
str.value = []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// watch()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box{
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.flex{
|
||||
margin-left: 68px;
|
||||
font-family: MicrosoftYaHei, '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
|
||||
.qcsx{
|
||||
margin-top: -3px;
|
||||
margin-left:40px;
|
||||
font-family: MicrosoftYaHei, '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.table{
|
||||
width: calc(90% + 40px);
|
||||
padding: 20px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__label{
|
||||
color: #999;
|
||||
}
|
||||
|
||||
::v-deep .el-checkbox-group {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
::v-deep .low-form {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
583
src/views/lowTemplate/generalPage/group/group_gjxqy.vue
Normal file
583
src/views/lowTemplate/generalPage/group/group_gjxqy.vue
Normal file
@@ -0,0 +1,583 @@
|
||||
<template>
|
||||
<div class="group_gjxqy_box">
|
||||
<!-- 顶部信息 -->
|
||||
<FormView
|
||||
formType="view"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="topInformation.defaultData"
|
||||
:showButton="false"
|
||||
:formId="topInformation.formId"
|
||||
></FormView>
|
||||
<!-- 分类页面X -->
|
||||
<el-tabs v-model="tabsOneActive" class="demo-tabs" @tab-click="handleClick">
|
||||
<template v-for="item in tabsOneList" :key="item.name">
|
||||
<el-tab-pane :label="item.label" :name="item.name"></el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
<!-- 流程进度 -->
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="text-16px">流程进度</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-steps :active="stepsActive" align-center>
|
||||
<template v-for="item in stepsList" :key="item.id">
|
||||
<el-step
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:class="item.id == stepsActive ? 'stepsActive' : ''"
|
||||
/>
|
||||
</template>
|
||||
</el-steps>
|
||||
</el-card>
|
||||
<!-- 操作记录 -->
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="text-16px">操作记录</span>
|
||||
</div>
|
||||
</template>
|
||||
<FormView
|
||||
formType="view"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="operationRecord.defaultData"
|
||||
:showButton="false"
|
||||
:formId="operationRecord.formId"
|
||||
></FormView>
|
||||
</el-card>
|
||||
<!-- 操作记录流程 -->
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="text-16px">操作记录</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-steps
|
||||
direction="vertical"
|
||||
:active="stepsVertical.length"
|
||||
align-center
|
||||
class="steps-vertical-class"
|
||||
>
|
||||
<template v-for="item in stepsVertical" :key="item.id">
|
||||
<el-step :title="item.title">
|
||||
<template #description>
|
||||
<el-card>
|
||||
<div>
|
||||
<el-avatar :size="34" :src="item.cardContent.avatarImg" />
|
||||
</div>
|
||||
<div class="ml-5px grid">
|
||||
<div class="flex items-center">
|
||||
<p class="c-#bcaeae m-0 font">{{ item.cardContent.username }}</p>
|
||||
<p class="pl-5px m-0 c-#999 text-11px font-family">{{
|
||||
item.cardContent.userjob
|
||||
}}</p>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center c-#bcaeae text-11px font-family"
|
||||
style="color: #666"
|
||||
>
|
||||
<p class="m-0">{{ item.cardContent.examineApproveGrade }}</p>
|
||||
<p class="pl-3px m-0">{{ item.cardContent.examineApproveName }}</p>
|
||||
<p class="pl-3px m-0">{{ item.cardContent.examineApproveResult }}</p>
|
||||
</div>
|
||||
<div class="flex items-center c-#999">
|
||||
<el-icon><Clock /></el-icon>
|
||||
<p class="pl-3px m-0" style="font-family: '微软雅黑', sans-serif">{{
|
||||
item.cardContent.examineApproveTime
|
||||
}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-auto c-#bcaeae text-11px font" style="font-size: 12px">
|
||||
{{ item.cardContent.type }}
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</el-step>
|
||||
</template>
|
||||
</el-steps>
|
||||
</el-card>
|
||||
<!-- 日志记录 -->
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="text-16px">日志记录</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty description="未查询到相关数据" image="/img/img5.svg" />
|
||||
</el-card>
|
||||
<!-- 分类标签 -->
|
||||
<el-card class="card-and-tabs">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-tabs v-model="tabsOneActive" @tab-click="handleClick">
|
||||
<template v-for="item in tabsOneList" :key="item.name">
|
||||
<el-tab-pane :label="item.label" :name="item.name"></el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<LowTable :tableId="tabsTableData.tabId" :enhanceData="tabsTableData.enhanceData"></LowTable>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormView, LowTable } from '@/components/LowDesign/index'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
import { Clock } from '@element-plus/icons-vue'
|
||||
|
||||
defineOptions({ name: 'GroupGjxqy' })
|
||||
|
||||
const topInformationData = reactive({
|
||||
create_date: '2024-09-11 09:04:00',
|
||||
customer_rating: 4,
|
||||
customer_source: '7',
|
||||
customer_status: '3',
|
||||
fields_4858197: '',
|
||||
fields_5452530: '',
|
||||
head_contact: '1833323260582391810',
|
||||
system_number: '202010001',
|
||||
tabs_title: '广州某科技有限公司',
|
||||
type_of_industry: '3',
|
||||
vesting_officer: '1833323260582391810'
|
||||
})
|
||||
|
||||
const topInformation = ref({
|
||||
title: '基本信息',
|
||||
formId: '1838820624684339201',
|
||||
defaultData: topInformationData
|
||||
})
|
||||
|
||||
const tabsOneActive = ref('1')
|
||||
const tabsOneList = ref([
|
||||
{ label: '分类页面一', name: '1' },
|
||||
{ label: '分类页面二', name: '2' },
|
||||
{ label: '分类页面三', name: '3' },
|
||||
{ label: '分类页面四', name: '4' }
|
||||
])
|
||||
|
||||
const stepsActive = ref(4)
|
||||
const stepsList = ref([
|
||||
{ id: 1, title: '提交申请', description: '2024-09-24 15:03' },
|
||||
{ id: 2, title: '部门审批', description: '2024-09-24 15:03' },
|
||||
{ id: 3, title: '行政审批', description: '2024-09-24 15:03' },
|
||||
{ id: 4, title: '申请成功', description: '2024-09-24 15:03' }
|
||||
])
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
|
||||
const operationRecordData = ref({
|
||||
tabs_customer_name: '广州某科技有限公司',
|
||||
tabs_customer_rating: 4,
|
||||
tabs_customer_source: '7',
|
||||
tabs_customer_status: '3',
|
||||
tabs_now_city: '130000',
|
||||
tabs_system_number: '202010001',
|
||||
tabs_type_of_industry: '3',
|
||||
tabs_update_date: '2024-09-25 00:00:00',
|
||||
tabs_vesting_officer: '1833323260582391810',
|
||||
tabs_company_tax: '91440300MA5FQY****',
|
||||
tabs_company_tel: '3666-756614',
|
||||
tabs_company_web: 'http://www.xx.com',
|
||||
tabs_current_title: '5',
|
||||
tabs_deposit_bank: '交通银行深圳井南山支行',
|
||||
tabs_detailed_address: '广东省深圳市南山区',
|
||||
tabs_head_contact: '李小红',
|
||||
tabs_invoice_title: '广州某科技有限公司',
|
||||
tabs_phone_number: '15238683333'
|
||||
})
|
||||
|
||||
const operationRecord = ref({
|
||||
title: '操作记录',
|
||||
formId: '1838842046060228609',
|
||||
defaultData: operationRecordData
|
||||
})
|
||||
|
||||
const stepsVertical = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '09-24',
|
||||
cardContent: {
|
||||
avatarImg: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
|
||||
username: '赵小刚',
|
||||
userjob: '销售经理',
|
||||
examineApproveGrade: '1级审批人',
|
||||
examineApproveName: '李小明',
|
||||
examineApproveResult: '审批通过,通过原因:无',
|
||||
examineApproveTime: '2019-08-23 22:31',
|
||||
type: '订单'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '09-24',
|
||||
cardContent: {
|
||||
avatarImg: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
|
||||
username: '赵小刚',
|
||||
userjob: '销售经理',
|
||||
examineApproveGrade: '1级审批人',
|
||||
examineApproveName: '李小明',
|
||||
examineApproveResult: '审批通过,通过原因:无',
|
||||
examineApproveTime: '2019-08-23 22:31',
|
||||
type: '订单'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '09-24',
|
||||
cardContent: {
|
||||
avatarImg: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
|
||||
username: '赵小刚',
|
||||
userjob: '销售经理',
|
||||
examineApproveGrade: '1级审批人',
|
||||
examineApproveName: '李小明',
|
||||
examineApproveResult: '审批通过,通过原因:无',
|
||||
examineApproveTime: '2019-08-23 22:31',
|
||||
type: '订单'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '09-24',
|
||||
cardContent: {
|
||||
avatarImg: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
|
||||
username: '赵小刚',
|
||||
userjob: '销售经理',
|
||||
examineApproveGrade: '1级审批人',
|
||||
examineApproveName: '李小明',
|
||||
examineApproveResult: '审批通过,通过原因:无',
|
||||
examineApproveTime: '2019-08-23 22:31',
|
||||
type: '订单'
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const tabsTableData = ref({
|
||||
tabId: '1840268794366795777',
|
||||
enhanceData: {
|
||||
buttonType: 'hidden'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.group_gjxqy_box {
|
||||
.el-tabs {
|
||||
::v-deep(.el-tabs__header) {
|
||||
margin-bottom: 0;
|
||||
background-color: white;
|
||||
|
||||
.el-tabs__nav-wrap {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-top: none;
|
||||
|
||||
.el-tabs__active-bar {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
margin-bottom: 15px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
|
||||
&:hover {
|
||||
color: rgb(64 158 255);
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__item.is-active {
|
||||
color: rgb(64 158 255);
|
||||
}
|
||||
|
||||
&::after {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-card) {
|
||||
width: 95%;
|
||||
margin: 20px auto 0;
|
||||
border-radius: 10px;
|
||||
|
||||
.el-card__header {
|
||||
height: 60px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
background-color: rgb(249 249 249 / 100%);
|
||||
border-bottom: 1px solid rgb(233 233 233 / 100%);
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
.el-steps {
|
||||
height: 90px;
|
||||
padding-top: 10px;
|
||||
|
||||
.el-step {
|
||||
.el-step__head {
|
||||
.el-step__icon {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border: 3px solid;
|
||||
|
||||
.el-step__icon-inner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-step__main {
|
||||
.el-step__title {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 30px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.el-step__description {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
&.stepsActive {
|
||||
.el-step__head {
|
||||
.el-step__icon {
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.steps-vertical-class {
|
||||
height: auto;
|
||||
|
||||
.el-step__head {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-color: #f2f2f2;
|
||||
|
||||
.el-step__line {
|
||||
bottom: -50%;
|
||||
left: 11.5px;
|
||||
background-color: #f2f2f200;
|
||||
}
|
||||
|
||||
.el-step__icon {
|
||||
width: 9px !important;
|
||||
height: 9px !important;
|
||||
border-width: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-step__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 125px;
|
||||
margin-top: 10px;
|
||||
|
||||
.el-step__title {
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.el-step__description {
|
||||
width: 100%;
|
||||
padding-right: 0;
|
||||
|
||||
.el-card {
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 9.8%);
|
||||
|
||||
.el-card__body {
|
||||
display: flex;
|
||||
height: calc(100% - 16px);
|
||||
padding: 8px 30px 8px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-empty {
|
||||
.el-empty__image {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.el-empty__description > p {
|
||||
font-size: 13px;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.avue-crud {
|
||||
.el-card {
|
||||
border-radius: 0;
|
||||
|
||||
.el-form {
|
||||
.el-table__header-wrapper {
|
||||
.el-table__header {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
|
||||
.el-table__cell {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__body {
|
||||
.el-table__row {
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.el-table__cell {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
|
||||
.el-button {
|
||||
.el-icon {
|
||||
line-height: 0.9em;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__row--striped .el-table__cell {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.card-and-tabs {
|
||||
.el-card__header {
|
||||
height: 60px;
|
||||
padding: 0 20px 0 0;
|
||||
|
||||
.el-tabs {
|
||||
--el-tabs-header-height: 60px;
|
||||
|
||||
.el-tabs__header {
|
||||
background-color: rgb(249 249 249);
|
||||
|
||||
.el-tabs__nav-wrap {
|
||||
height: 60px;
|
||||
margin-bottom: 0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding-bottom: 40px;
|
||||
|
||||
.el-card {
|
||||
width: calc(100% - 10px);
|
||||
padding: 0 5px;
|
||||
|
||||
.el-card__body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-form-item {
|
||||
.el-form-item__label {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
.el-input__inner {
|
||||
font-family: MicrosoftYaHei, '微软雅黑', sans-serif;
|
||||
font-weight: 400;
|
||||
color: #666 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .avue-title p {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important;
|
||||
font-size: 18px !important;
|
||||
font-weight: 700 !important;
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
::v-deep .demo-tabs .el-tabs__nav {
|
||||
margin-left: 25px;
|
||||
|
||||
.el-tabs__item {
|
||||
margin-bottom: 15px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.el-tabs__active-bar {
|
||||
left: -1px;
|
||||
height: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .card-header .el-tabs__nav {
|
||||
// margin-left: 25px;
|
||||
|
||||
.el-tabs__item {
|
||||
margin-bottom: 0 !important;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.el-tabs__active-bar {
|
||||
left: 2px;
|
||||
height: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.font {
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.font-family {
|
||||
font-family: MicrosoftYaHei, '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// .fonts{
|
||||
// font-family: 'MicrosoftYaHei', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
// font-weight: 400;
|
||||
// font-style: normal;
|
||||
// font-size: 12px;
|
||||
// }
|
||||
</style>
|
||||
92
src/views/lowTemplate/generalPage/group/group_jcxqy.vue
Normal file
92
src/views/lowTemplate/generalPage/group/group_jcxqy.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<!-- 基础详情页 -->
|
||||
<ContentWrap>
|
||||
<div v-for="item in stepList" :key="item.refKey">
|
||||
<div class="pl-8px b-l-6px b-solid b-#409EFF mb-15px title">{{ item.title }}</div>
|
||||
<div class="mb-20px" v-if="item.type == 'exploit'">
|
||||
<FormView
|
||||
formType="view"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="item.defaultData"
|
||||
:showButton="false"
|
||||
:formId="item.formId"
|
||||
></FormView>
|
||||
</div>
|
||||
<div class="mt-20px" v-if="item.type == 'design'">
|
||||
<LowTable :tableId="item.tableId" :enhanceData="item.enhanceData"></LowTable>
|
||||
</div>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormView, LowTable } from '@/components/LowDesign/index'
|
||||
|
||||
defineOptions({ name: 'GroupJcxqy' })
|
||||
const basicDefaultData = reactive({
|
||||
customer_name: '广州某科技有限公司',
|
||||
customer_rating: 3,
|
||||
customer_source: '3',
|
||||
customer_status: '4',
|
||||
now_city: '120000,120100,120102',
|
||||
system_number: '202010033',
|
||||
type_of_industry: '4',
|
||||
update_date: '2024-01-23 23:23:23',
|
||||
vesting_officer: '1836227753657548801'
|
||||
})
|
||||
|
||||
const detailDefaultData = reactive({
|
||||
company_tax: '91645530MA5FQY4QGB',
|
||||
company_tel: '15238680350',
|
||||
company_web: 'http://www.xx.cn',
|
||||
current_title: '2',
|
||||
deposit_bank: '建设银行广州白云支行',
|
||||
detailed_address: '440000,440100,440111',
|
||||
head_contact: '1836227753657548801',
|
||||
invoice_title: '发票抬头',
|
||||
phone_number: '15239683355'
|
||||
})
|
||||
|
||||
const financialEnhanceData = reactive({
|
||||
hideHeader: 'disabled'
|
||||
})
|
||||
|
||||
const stepList = ref([
|
||||
{
|
||||
refKey: 'basic',
|
||||
title: '基本信息',
|
||||
type: 'exploit',
|
||||
formId: '1833063456320286721',
|
||||
defaultData: basicDefaultData
|
||||
},
|
||||
{
|
||||
refKey: 'detail',
|
||||
title: '详细信息',
|
||||
type: 'exploit',
|
||||
formId: '1833076541672169474',
|
||||
defaultData: detailDefaultData
|
||||
},
|
||||
{
|
||||
refKey: 'financial',
|
||||
title: '下级客户',
|
||||
type: 'design',
|
||||
tableId: '1833323260582391810',
|
||||
enhanceData: financialEnhanceData
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
height: 18px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 18px;
|
||||
color: #666670;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
</style>
|
||||
57
src/views/lowTemplate/generalPage/group/group_sjbby.vue
Normal file
57
src/views/lowTemplate/generalPage/group/group_sjbby.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<!-- tabs标签页风格表单开发示例 -->
|
||||
<el-tabs v-model="activeName" type="border-card" class="demo-tabs" @tab-click="handleClick">
|
||||
<template v-for="item in tabsPaneList" :key="item.name">
|
||||
<el-tab-pane :label="item.label" :name="item.name">
|
||||
<LowTable
|
||||
:ref="(el) => (tableRef[item.name] = el)"
|
||||
:tableId="item.tableId"
|
||||
:calcHeight="item.calcHeight || undefined"
|
||||
:enhanceData="item.enhanceData"
|
||||
></LowTable>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LowTable } from '@/components/LowDesign/index'
|
||||
|
||||
defineOptions({ name: 'GroupSjbby' })
|
||||
|
||||
const activeName = ref('cjje')
|
||||
const tableRef = ref({})
|
||||
|
||||
const tabsPaneList = ref([
|
||||
{
|
||||
label: '成交金额',
|
||||
name: 'cjje',
|
||||
tableId: '1834136609367400450',
|
||||
enhanceData: { hideHeader: 'disabled' }
|
||||
},
|
||||
{ label: '回款金额', name: 'hkje', tableId: '1851509263837597697', calcHeight: 200 }
|
||||
])
|
||||
|
||||
const handleClick = (tab) => {
|
||||
const key = tab.props.name
|
||||
if (key == 'hkje' && tableRef.value[key]) tableRef.value[key].initTableLayout()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.demo-tabs > .el-tabs__content {
|
||||
padding: 32px;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #6b778c;
|
||||
}
|
||||
|
||||
.demo-tabs {
|
||||
::v-deep(.el-tabs__nav-wrap) {
|
||||
.el-tabs__item {
|
||||
height: 50px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
7
src/views/lowTemplate/generalPage/group/group_zlsz.vue
Normal file
7
src/views/lowTemplate/generalPage/group/group_zlsz.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div> 高级筛选页 </div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
94
src/views/lowTemplate/generalPage/journal/journal.vue
Normal file
94
src/views/lowTemplate/generalPage/journal/journal.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="w-100%">
|
||||
<div class="box w-100%">
|
||||
<div class="w-[calc(100%-60px)] header">
|
||||
<el-radio-group v-model="radio" >
|
||||
<template v-for="(item,index) in tabbleDate" :key="index">
|
||||
<el-radio-button :label="item.label" :value="index"/>
|
||||
</template>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="tabel">
|
||||
<LowTable :ref="(el)=>(customRef = el)" :tableId="tabbleDate[radio].tableId" :enhanceData="tabbleDate[radio].enhanceData" > </LowTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
const radio = ref(0)
|
||||
const customRef = ref()
|
||||
const tabbleDate = ref([
|
||||
{
|
||||
label:'操作日志',
|
||||
type:'table',
|
||||
defaultData:{},
|
||||
tableId:'1854067250698612737',
|
||||
enhanceData: { hideHeader: 'disabled'}
|
||||
},
|
||||
{
|
||||
label:'审批日志',
|
||||
type:'table',
|
||||
defaultData:{},
|
||||
tableId:'1854331399533998082',
|
||||
enhanceData: { hideHeader: 'disabled'}
|
||||
},
|
||||
{
|
||||
label:'登录日志',
|
||||
type:'table',
|
||||
defaultData:{},
|
||||
tableId:'1854341001851940865',
|
||||
enhanceData: { hideHeader: 'disabled'}
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box{
|
||||
background-color: rgb(255 255 255 / 100%);
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 4.71%);
|
||||
box-sizing: border-box;
|
||||
|
||||
.header{
|
||||
display: flex;
|
||||
height: 60px;
|
||||
padding: 0 30px;
|
||||
background-color: rgb(249 249 249 / 100%);
|
||||
border-bottom: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 10px 10px 0 0;
|
||||
align-content: center;;
|
||||
|
||||
::v-deep(.el-radio-group){
|
||||
.el-radio-button{
|
||||
.el-radio-button__inner{
|
||||
display: flex;
|
||||
width: 101px;
|
||||
height: 35px;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
color: #999;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
.el-radio-button__inner{
|
||||
color: #FFF ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabel{
|
||||
padding: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
340
src/views/lowTemplate/generalPage/post/postForm.vue
Normal file
340
src/views/lowTemplate/generalPage/post/postForm.vue
Normal file
@@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<div class="step-orde-form-box">
|
||||
<div class="order-top">
|
||||
<div style="display: flex">
|
||||
<div class="tb-box">
|
||||
<img src="/img/zwgl.jpg" style="height: 30px" />
|
||||
</div>
|
||||
<div class="title-box">
|
||||
<p class="title">销售总监</p>
|
||||
<div class="text-box">
|
||||
<span>暂无相关描述</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormView
|
||||
:ref="(el) => (formViewRef[orderTopData.refKey] = el)"
|
||||
formType="add"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="orderTopData.defaultData"
|
||||
:showButton="false"
|
||||
:formId="orderTopData.formId"
|
||||
></FormView>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<template v-for="(item, index) in data" :key="index">
|
||||
<el-tab-pane :label="item.label" :name="item.name">
|
||||
<div class="tabs-box">
|
||||
<FormView
|
||||
v-if="item.formId"
|
||||
formType="add"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="item.defaultData"
|
||||
:showButton="true"
|
||||
:formId="item.formId"
|
||||
></FormView>
|
||||
<template v-if="item.tableId">
|
||||
<LowTable :tableId="item.tableId" :enhanceData="item.enhanceData"> </LowTable>
|
||||
</template>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
|
||||
interface Props {
|
||||
type?: string //控件名称
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const orderTopData = ref({
|
||||
refKey: 'gdgl',
|
||||
formId: '1846114280447025153',
|
||||
defaultData: {
|
||||
gdbh: '202062976600',
|
||||
gdbt: '处理客户反馈问题',
|
||||
gdzt: 1,
|
||||
lx: props.type
|
||||
}
|
||||
})
|
||||
const formViewRef = ref({})
|
||||
|
||||
const activeName = ref('first')
|
||||
|
||||
const data = reactive([
|
||||
{
|
||||
label: '操作权限',
|
||||
name: 'first',
|
||||
defaultData: {},
|
||||
formId: '1846364734045270018'
|
||||
},
|
||||
{
|
||||
label: '数据权限',
|
||||
name: 'second',
|
||||
defaultData: {},
|
||||
formId: '1846383339671629825'
|
||||
},
|
||||
{
|
||||
label: '字段权限',
|
||||
name: 'third',
|
||||
defaultData: {},
|
||||
formId: '1846399447287967745'
|
||||
},
|
||||
{
|
||||
label: '成员列表',
|
||||
name: 'fourth',
|
||||
defaultData: {},
|
||||
tableId: '1846429439782793217',
|
||||
enhanceData: { hideHeader: 'disabled' }
|
||||
},
|
||||
{
|
||||
label: '操作记录',
|
||||
name: 'czjl',
|
||||
defaultData: {},
|
||||
tableId: '1846103236139950082',
|
||||
enhanceData: { hideHeader: 'disabled' }
|
||||
}
|
||||
])
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.step-orde-form-box {
|
||||
.order-top {
|
||||
display: flex;
|
||||
height: 110px;
|
||||
padding: 20px 25px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 4.71%);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.tb-box {
|
||||
display: flex;
|
||||
width: 65px;
|
||||
height: 65px;
|
||||
line-height: 65px;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
background: #fb6260;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-box {
|
||||
display: flex;
|
||||
height: 60px;
|
||||
margin-left: 15px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.title {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
// margin-bottom: 5px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-box {
|
||||
display: flex;
|
||||
font-family: '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
|
||||
.text-box-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.dian {
|
||||
display: inline-block;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
margin-right: 5px;
|
||||
margin-left: 10px;
|
||||
background: #f90;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.steps {
|
||||
width: calc(90% + 40px);
|
||||
padding: 60px 0 30px;
|
||||
margin: 0 auto 20px;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 10px;
|
||||
|
||||
::v-deep(.el-steps) {
|
||||
.el-step {
|
||||
.el-step__head.is-process {
|
||||
color: #409eff;
|
||||
|
||||
.el-step__icon.is-text {
|
||||
color: white;
|
||||
background-color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-step__head {
|
||||
.el-step__line {
|
||||
top: 13px;
|
||||
}
|
||||
|
||||
.el-step__icon.is-text {
|
||||
width: 25px;
|
||||
height: 30px;
|
||||
|
||||
.el-step__icon-inner {
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-step__main {
|
||||
.el-step__title {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 25px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.el-step__description {
|
||||
font-size: 13px;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .card > .el-card {
|
||||
width: calc(90% + 40px);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
|
||||
// &>::v-deep(.crud){
|
||||
// width: calc(90% + 40px);
|
||||
// }
|
||||
|
||||
::v-deep(.el-card) {
|
||||
// width: calc(90% + 40px);
|
||||
|
||||
.el-card__header {
|
||||
background-color: #f9f9f9;
|
||||
|
||||
.card-header {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
.avue-crud {
|
||||
.el-card {
|
||||
margin: 0 auto;
|
||||
border-radius: 0;
|
||||
|
||||
.el-table__header {
|
||||
height: 55px;
|
||||
|
||||
th {
|
||||
height: 55px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__body {
|
||||
.el-table__row {
|
||||
.el-table__cell {
|
||||
font-size: 13px;
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
// padding: 0px 10p;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 60px;
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px rgb(11 11 11 / 5.7%);
|
||||
|
||||
::v-deep .el-tabs__nav-wrap::after {
|
||||
// background: none !important;
|
||||
}
|
||||
|
||||
.tabs-box {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
::v-deep .el-tabs__nav-scroll {
|
||||
display: flex;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
background: #f9f9f9;
|
||||
align-items: center;
|
||||
border-radius: 10px 10px 0 0;
|
||||
|
||||
.el-tabs__nav {
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
|
||||
.el-tabs__item {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.is-active {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
21
src/views/lowTemplate/generalPage/project/jdt.vue
Normal file
21
src/views/lowTemplate/generalPage/project/jdt.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div >
|
||||
<div class="box">
|
||||
<el-progress :percentage="jdt" :stroke-width="11"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
interface Props {
|
||||
jdt?: number //控件名称
|
||||
}
|
||||
const props =defineProps<Props>()
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box{
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
138
src/views/lowTemplate/generalPage/project/project.vue
Normal file
138
src/views/lowTemplate/generalPage/project/project.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="w-100%">
|
||||
<header class="header ">
|
||||
<div class="list" v-for="(item,index) in listData" :key="index">
|
||||
<p class="title">{{item.title}}</p>
|
||||
<p class="text">{{item.num}}</p>
|
||||
</div>
|
||||
</header>
|
||||
<footer class="content">
|
||||
<div class="content-top" >
|
||||
<el-radio-group v-model="radio1" size="large">
|
||||
<el-radio-button label="全部项目" value="qbxm" />
|
||||
<el-radio-button label="进行中" value="jxz" />
|
||||
<el-radio-button label="已完成" value="ywc" />
|
||||
<el-radio-button label="已延期" value="yyq" />
|
||||
<el-radio-button label="已取消" value="yqx" />
|
||||
</el-radio-group>
|
||||
<el-input
|
||||
v-model="input"
|
||||
style="width: 200px;height: 35px;margin-left:20px;"
|
||||
size="large"
|
||||
placeholder="输入搜索关键词"
|
||||
:suffix-icon="Search"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="table">
|
||||
</div>
|
||||
<LowTable :tableId="data.tableId" :enhanceData="data.enhanceData" > </LowTable>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
|
||||
const listData = reactive([
|
||||
{title:'项目总数',num:'200'},
|
||||
{title:'进行中',num:'50'},
|
||||
{title:'已完成',num:'50'},
|
||||
{title:'已延期',num:'50'},
|
||||
{title:'已取消',num:'50'},
|
||||
])
|
||||
|
||||
const data = reactive({
|
||||
label:'全部项目',
|
||||
name:'qbxm',
|
||||
defaultData:{},
|
||||
tableId:'1847094468974710786',
|
||||
type:'table',
|
||||
enhanceData: { hideHeader: 'disabled'}
|
||||
})
|
||||
|
||||
const radio1 = ref('qbxm')
|
||||
const input = ref('')
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header{
|
||||
display: flex;
|
||||
height: 150px;
|
||||
padding: 0 20px;
|
||||
margin-bottom:20px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.list{
|
||||
width: 292px;
|
||||
width: 100%;
|
||||
|
||||
p{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title{
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 42px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.text{
|
||||
font-family: '微软雅黑 Bold', '微软雅黑', sans-serif;
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
line-height: 42px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content{
|
||||
position: relative;
|
||||
// height: 600px;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
|
||||
.content-top{
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
::v-deep(.el-radio-group) {
|
||||
.el-radio-button {
|
||||
.el-radio-button__inner {
|
||||
display: flex;
|
||||
width: 101px;
|
||||
height: 35px;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&.is-active{
|
||||
.el-radio-button__inner {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table{
|
||||
// position: absolute;
|
||||
margin-top:10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
310
src/views/lowTemplate/generalPage/project/projectForm.vue
Normal file
310
src/views/lowTemplate/generalPage/project/projectForm.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<div class="step-orde-form-box">
|
||||
<div class="order-top">
|
||||
<FormView
|
||||
:ref="(el) => (formViewRef[orderTopData.refKey] = el)"
|
||||
formType="add"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="orderTopData.defaultData"
|
||||
:showButton="false"
|
||||
:formId="orderTopData.formId"
|
||||
></FormView>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<template v-for="(item,index) in data" :key="index">
|
||||
<el-tab-pane :label="item.label" :name="item.name">
|
||||
<div class="tabs-box">
|
||||
<upDate v-if="item.type == 'jurisdiction'"></upDate>
|
||||
<template v-if="item.type == 'table'">
|
||||
<LowTable :tableId="item.tableId" :enhanceData="item.enhanceData" > </LowTable>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
import upDate from './update.vue'
|
||||
|
||||
interface Props {
|
||||
type?: string //控件名称
|
||||
}
|
||||
const props =defineProps<Props>()
|
||||
|
||||
const orderTopData = ref({
|
||||
type:'table',
|
||||
refKey: 'gdgl',
|
||||
formId: '1847209781133185025',
|
||||
defaultData: {},
|
||||
enhanceData: { hideHeader: 'disabled'}
|
||||
})
|
||||
|
||||
const formViewRef = ref({})
|
||||
|
||||
const activeName = ref('first')
|
||||
|
||||
const data = reactive([
|
||||
{label:'任务列表',name:'first',defaultData:{},tableId:'1847472439438737409',type:'table'},
|
||||
{label:'更新记录',name:'second',defaultData:{},tableId:'',type:'jurisdiction'},
|
||||
{label:'附件记录',name:'third',defaultData:{},tableId:'1845012516400803842',type:'table',enhanceData: { hideHeader: 'disabled'}},
|
||||
{label:'成员列表',name:'fourth',defaultData:{},tableId:'1848301752228683777',type:'table',enhanceData: { hideHeader: 'disabled'}},
|
||||
])
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.step-orde-form-box {
|
||||
.order-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px 25px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff;
|
||||
// border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px rgb(11 11 11 / 5.7%);
|
||||
|
||||
.tb-box {
|
||||
display: flex;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
background: #fb6260;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-box {
|
||||
margin-left: 10px;
|
||||
|
||||
.title {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
// margin-bottom: 5px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-box {
|
||||
display: flex;
|
||||
font-family: '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
|
||||
.text-box-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.dian {
|
||||
display: inline-block;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
margin-right: 5px;
|
||||
margin-left: 10px;
|
||||
background: #f90;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.steps {
|
||||
width: calc(90% + 40px);
|
||||
padding: 60px 0 30px;
|
||||
margin: 0 auto 20px;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 10px;
|
||||
|
||||
::v-deep(.el-steps) {
|
||||
.el-step {
|
||||
.el-step__head.is-process {
|
||||
color: #409eff;
|
||||
|
||||
.el-step__icon.is-text {
|
||||
color: white;
|
||||
background-color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-step__head {
|
||||
.el-step__line {
|
||||
top: 13px;
|
||||
}
|
||||
|
||||
.el-step__icon.is-text {
|
||||
width: 25px;
|
||||
height: 30px;
|
||||
|
||||
.el-step__icon-inner {
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-step__main {
|
||||
.el-step__title {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 25px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.el-step__description {
|
||||
font-size: 13px;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .card > .el-card {
|
||||
width: calc(90% + 40px);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
|
||||
// &>::v-deep(.crud){
|
||||
// width: calc(90% + 40px);
|
||||
// }
|
||||
|
||||
::v-deep(.el-card) {
|
||||
// width: calc(90% + 40px);
|
||||
|
||||
.el-card__header {
|
||||
background-color: #f9f9f9;
|
||||
|
||||
.card-header {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
.avue-crud {
|
||||
.el-card {
|
||||
margin: 0 auto;
|
||||
border-radius: 0;
|
||||
|
||||
.el-table__header {
|
||||
height: 55px;
|
||||
|
||||
th {
|
||||
height: 55px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__body {
|
||||
.el-table__row {
|
||||
.el-table__cell {
|
||||
font-size: 13px;
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer{
|
||||
// padding: 0px 10p;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 60px;
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px rgb(11 11 11 / 5.7%);
|
||||
|
||||
::v-deep .el-tabs__nav-wrap::after {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
::v-deep .el-tabs__nav-scroll {
|
||||
background: #f9f9f9 !important;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.tabs-box{
|
||||
padding: 0 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-tabs__item{
|
||||
font-family: '微软雅黑 Bold', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
::v-deep th.el-table__cell {
|
||||
height: 55px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑', sans-serif;
|
||||
font-weight: 700;
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
::v-deep .el-tabs .el-tabs__nav-wrap {
|
||||
height: 60px;
|
||||
|
||||
.el-tabs__nav{
|
||||
height: 60px;
|
||||
|
||||
.el-tabs__item{
|
||||
height: 60px;
|
||||
// line-height: 60px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #666;
|
||||
|
||||
&.is-active{
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
305
src/views/lowTemplate/generalPage/project/update.vue
Normal file
305
src/views/lowTemplate/generalPage/project/update.vue
Normal file
@@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="footer">
|
||||
<!-- <div style="display: flex; align-items: center; margin-bottom: 20px">
|
||||
<span class="span"></span><span>操作记录</span>
|
||||
</div> -->
|
||||
<div class="footer-list">
|
||||
<el-steps :space="150" :active="czjlData.length" direction="vertical">
|
||||
<template v-for="(item, index) in czjlData" :key="index">
|
||||
<el-step :title="item.title" :description="item.description" last>
|
||||
<template #title>
|
||||
<div class="content flex h-100px">
|
||||
<div class="img mt-10px"
|
||||
><Icon icon="mingcute:user-3-fill" :size="25" style="color: #fff"
|
||||
/></div>
|
||||
<div class="w-[calc(100%-45px)] mt-6px mb-6px grid grid-rows-3">
|
||||
<div class="content-top">
|
||||
<div class="list-box-top-left">
|
||||
<span style="font-size: 14px; color: #666">{{ item.name }}</span>
|
||||
<p class="font" style="margin-left: 5px">{{ item.text }}</p>
|
||||
</div>
|
||||
<div class="list-box-top-right pr-20px mt-30px">
|
||||
<!-- <div> -->
|
||||
<span class="box"></span>
|
||||
<span style="margin: 0 10px 0 5px">正常</span>
|
||||
<span>完成进度:50%</span>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="content-bottom"> -->
|
||||
<div>
|
||||
<p class="text">{{ item.content }}</p>
|
||||
</div>
|
||||
<div class="lzgd">
|
||||
<Icon icon="mingcute:time-line" :size="14" style="color: #999" />
|
||||
<span style="color: #999 !important">{{ item.time }}</span>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-step>
|
||||
</template>
|
||||
</el-steps>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const czjlData = ref([
|
||||
{
|
||||
title: '',
|
||||
description: '08-23',
|
||||
text: '销售经理',
|
||||
name: '赵小刚',
|
||||
content:
|
||||
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
|
||||
gd: '工单标题',
|
||||
time: '2020-12-23 22:31'
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
description: '08-23',
|
||||
text: '销售经理',
|
||||
name: '赵小刚',
|
||||
content:
|
||||
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
|
||||
gd: '工单标题',
|
||||
time: '2020-12-23 22:31'
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
description: '08-23',
|
||||
text: '销售经理',
|
||||
name: '赵小刚',
|
||||
content:
|
||||
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
|
||||
gd: '工单标题',
|
||||
time: '2020-12-23 22:31'
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
description: '08-23',
|
||||
text: '销售经理',
|
||||
name: '赵小刚',
|
||||
content:
|
||||
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
|
||||
gd: '工单标题',
|
||||
time: '2020-12-23 22:31'
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
description: '08-23',
|
||||
text: '销售经理',
|
||||
name: '赵小刚',
|
||||
content:
|
||||
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
|
||||
gd: '工单标题',
|
||||
time: '2020-12-23 22:31'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.footer {
|
||||
margin: 30px 50px;
|
||||
margin-bottom: 50px;
|
||||
|
||||
.span {
|
||||
display: inline-block;
|
||||
width: 8.5px;
|
||||
height: 19px;
|
||||
margin-right: 10px;
|
||||
background: #409eff;
|
||||
}
|
||||
|
||||
.footer-list {
|
||||
width: 100%;
|
||||
// height: 200px;
|
||||
// padding: 20px;
|
||||
|
||||
::v-deep(.el-steps) {
|
||||
.el-step.is-vertical {
|
||||
.el-step__head {
|
||||
.el-step__line {
|
||||
width: 1px;
|
||||
margin-top: 25px;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.el-step__icon {
|
||||
width: 10px !important;
|
||||
height: 10px;
|
||||
margin-top: -20px;
|
||||
margin-left: 7px;
|
||||
overflow: hidden;
|
||||
|
||||
.el-step__icon-inner {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-step__main {
|
||||
margin-top: 2px;
|
||||
|
||||
.el-step__description.is-finish {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .el-step:last-child {
|
||||
// .el-step__head::after {
|
||||
// position: absolute;
|
||||
// top: 25px;
|
||||
// right: 11px;
|
||||
// z-index: 1;
|
||||
// display: block;
|
||||
// width: 1.6px;
|
||||
// height: 110px;
|
||||
// background-color: #f2f2f2;
|
||||
// border-color: red;
|
||||
// content: '';
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
top: 35px;
|
||||
left: 53px;
|
||||
width: 95.5%;
|
||||
padding:0 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 9.8%);
|
||||
|
||||
.img {
|
||||
display: flex;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
margin-right: 10px;
|
||||
background: #ccc;
|
||||
border-radius: 50%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.list-box-top-left {
|
||||
display: flex;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #999;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-box-top-right {
|
||||
display: flex;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: #666;
|
||||
align-items: center;
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: #51d351;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .content-bottom {
|
||||
// margin-top: -10px;
|
||||
// margin-left: 38px;
|
||||
// font-size: 12px;
|
||||
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 5px 0;
|
||||
font-family: MicrosoftYaHei, '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.lzgd {
|
||||
display: flex;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-steps) {
|
||||
.el-step__head {
|
||||
.el-step__line {
|
||||
background-color: var(--el-color-info-light-7);
|
||||
}
|
||||
|
||||
.el-step__icon {
|
||||
width: 30px !important;
|
||||
}
|
||||
|
||||
&.is-finish {
|
||||
margin-top: 5px;
|
||||
color: #f2f2f2 !important;
|
||||
border-color: #f2f2f2 !important;
|
||||
|
||||
.el-step__line {
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-step:not(:last-child) {
|
||||
.el-step__head {
|
||||
&.is-process::before {
|
||||
position: absolute;
|
||||
top: calc(50% - 1px);
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
width: 50%;
|
||||
height: 2px;
|
||||
background-color: var(--el-color-primary);
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-step__icon.is-text {
|
||||
// margin-top: -20px;
|
||||
color: #666 !important;
|
||||
border-color: #409eff !important;
|
||||
}
|
||||
|
||||
.font {
|
||||
font-family: MicrosoftYaHei, '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div class="article">
|
||||
<div
|
||||
class="w-[calc(100%-80px)] h-110px flex items-center bg-#f9f9f9 b-1px b-solid b-#e9e9e9 pl-40px pr-40px"
|
||||
>
|
||||
<div>
|
||||
<div class="h-36px flex items-center">
|
||||
<div class="font-wryh font-700 text-20px c-#666666 mr-15px">
|
||||
{{ data.htbt }}
|
||||
</div>
|
||||
<div class="flex mr-10px" v-if="data.sd && data.sd == '1'">
|
||||
<Icon icon="solar:lock-linear"></Icon>
|
||||
</div>
|
||||
<div
|
||||
class="w-52px h-20px bg-#0099ff rounded-2xl c-white flex justify-center items-center text-12px mr-10px"
|
||||
v-if="data.zd && data.zd == '1'"
|
||||
>
|
||||
置顶
|
||||
</div>
|
||||
<div
|
||||
class="w-52px h-20px bg-#ff7a8c rounded-2xl c-white flex justify-center items-center text-12px"
|
||||
v-if="data.rm && data.rm == '1'"
|
||||
>
|
||||
热门
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-38.4px flex items-center">
|
||||
<el-avatar :size="20" :src="data.cytx" />
|
||||
<span class="font-wryh ml-10px text-14px c-#999999">{{ data.$create_user }}</span>
|
||||
<span class="font-wryh ml-10px text-14px c-#999999 flex items-center">
|
||||
<Icon icon="mingcute:time-line" :size="17"></Icon>
|
||||
{{ data.create_time }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<el-button type="primary" plain style="width: 80px; height: 35px" @click="bjBtn">
|
||||
<Icon icon="fa6-solid:pen" :size="15" class="mr-2px"></Icon>
|
||||
编辑
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-[calc(100%-80px)] pl-40px pr-40px b-1px b-t-0 b-solid b-#e9e9e9">
|
||||
<div class="pt-30px" v-html="vHtml" style="line-height: 32px"> </div>
|
||||
<div class="w-100% text-right c-#999 font-wryh"> 最后编辑时间:2020-11-24 10:00:00 </div>
|
||||
|
||||
<div class="mt-50px">
|
||||
<div class="flex items-center mb-30px">
|
||||
<div class="w-6px h-18px bg-#409EFF mr-8px"></div>
|
||||
<span class="font-wryh font-700 c-#666666"> 发布评论 </span>
|
||||
</div>
|
||||
<div class="flex h-60px mb-10px bottom">
|
||||
<el-input type="text" placeholder="请输入批阅内容" v-model="inputVal" />
|
||||
<el-button type="primary" @click="fbBtn">发布</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<div class="flex items-center mt-40px mb-20px">
|
||||
<div class="w-6px h-18px bg-#409EFF mr-8px"></div>
|
||||
<span class="font-wryh font-700 c-#666666 mr-3px"> 评论记录 </span>
|
||||
<span class="font-400 c-#666666"> 10条 </span>
|
||||
</div>
|
||||
</div>
|
||||
<el-alert v-for="item in defineData" :key="item.id" type="info" :closable="false">
|
||||
<template #title>
|
||||
<el-avatar :size="28" class="mr-5px" :src="item.imgUrl" />
|
||||
<span class="c-#666666 mr-10px">{{ item.name }}</span>
|
||||
<span class="c-#999999 text-12px">{{ item.time }}</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<span class="c-#999999">{{ item.content }}</span>
|
||||
<div class="like flex">
|
||||
<Icon
|
||||
icon="iconamoon:like-bold"
|
||||
class="mr-2px cursor-pointer"
|
||||
:class="item.isLike ? 'c-red' : ''"
|
||||
@click="likeClick(item.id)"
|
||||
></Icon>
|
||||
{{ item.like }}
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
interface Data {
|
||||
htbt: string
|
||||
create_time: string
|
||||
$create_user: string
|
||||
cytx: string
|
||||
rm: string
|
||||
sd: string
|
||||
zd: string
|
||||
}
|
||||
|
||||
const prop = defineProps<{
|
||||
data: Data
|
||||
hideFun: Function
|
||||
}>()
|
||||
|
||||
const data = prop.data
|
||||
|
||||
const bjBtn = () => {
|
||||
prop.hideFun!()
|
||||
}
|
||||
|
||||
const vHtml = ref(
|
||||
'<div id="u90044_text" class="text "> <p><span>近日,在阿里云2020年云栖大会上,阿里云智能网络产品研究员祝顺民重磅发布了多款网络新品,其中之一就是应用负载均衡ALB。ALB产品定位应用层高级负载,具备超强性能、安全可靠、面向云原生、即开即用等优势价值,提供弹性自动伸缩、QUIC协议支持、基于内容的高级路由、自带DDoS安全防护、云原生应用、弹性灵活计费等产品能力,满足越来越多元化的应用层负载需求。</span></p><br><p><span>说到负载均衡,很多朋友或许都会想到经典负载均衡,阿里云负载均衡SLB发布近十年,为各行各业的用户提供强大稳定的负载分担能力,解决大并发流量负载分担,消除单点故障,提高业务可用性。但随着企业和互联网业务高速发展,业务形态和需求不断变化,诸多业务场景已经无法单纯的用传统负载均衡来满足全部需求。在大互联网业务、电商大促、音视频、移动互联网应用、游戏业务、金融服务、云原生开发应用等场景中,存在大量高性能、弹性、多协议七层转发、安全、云原生等需求,急需新产品设计来满足。</span></p><p><span><br></span></p> </div>'
|
||||
)
|
||||
|
||||
const defineData = ref([
|
||||
{
|
||||
id: 1,
|
||||
imgUrl:
|
||||
'https://oss.yckxt.com/chatgpt/upload/1/2024-11-06/1/f0ee8a3c7c9638a54940382568c9dpng_5.png',
|
||||
name: '赵小刚',
|
||||
time: '2019-03-23 22:31',
|
||||
content: '非常不错的分享,对我们的工作很有参考价值。',
|
||||
like: 100,
|
||||
isLike: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
imgUrl:
|
||||
'https://oss.yckxt.com/chatgpt/upload/1/2024-11-06/1/f0ee8a3c7c9638a54940382568c9dpng_5.png',
|
||||
name: '赵小刚',
|
||||
time: '2019-03-23 22:31',
|
||||
content: '非常不错的分享,对我们的工作很有参考价值。',
|
||||
like: 100,
|
||||
isLike: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
imgUrl:
|
||||
'https://oss.yckxt.com/chatgpt/upload/1/2024-11-06/1/f0ee8a3c7c9638a54940382568c9dpng_5.png',
|
||||
name: '赵小刚',
|
||||
time: '2019-03-23 22:31',
|
||||
content: '非常不错的分享,对我们的工作很有参考价值。',
|
||||
like: 100,
|
||||
isLike: false
|
||||
}
|
||||
])
|
||||
|
||||
let likeIdList = ref<number[]>([])
|
||||
const inputVal = ref('')
|
||||
let dataLength = ref(0)
|
||||
const fbBtn = () => {
|
||||
if (!inputVal.value) return
|
||||
dataLength.value = defineData.value?.length || 0
|
||||
let endVal = defineData.value![dataLength.value - 1]
|
||||
let id = endVal.id + 1
|
||||
defineData.value![dataLength.value] = {
|
||||
id: id,
|
||||
imgUrl:
|
||||
'https://oss.yckxt.com/chatgpt/upload/1/2024-11-06/1/f0ee8a3c7c9638a54940382568c9dpng_5.png',
|
||||
name: '赵小刚',
|
||||
time: formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss'),
|
||||
content: inputVal.value,
|
||||
like: 0,
|
||||
isLike: false
|
||||
}
|
||||
inputVal.value = ''
|
||||
}
|
||||
|
||||
const likeClick = (id) => {
|
||||
defineData.value = defineData.value.map((item) => {
|
||||
if (id == item.id) {
|
||||
let index = likeIdList.value.indexOf(id)
|
||||
if (index == -1) {
|
||||
likeIdList.value.push(item.id)
|
||||
return {
|
||||
...item,
|
||||
like: item.like + 1,
|
||||
isLike: true
|
||||
}
|
||||
} else {
|
||||
likeIdList.value.splice(index, 1)
|
||||
return {
|
||||
...item,
|
||||
like: item.like - 1,
|
||||
isLike: false
|
||||
}
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.article {
|
||||
.font-wryh {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
::v-deep(.el-input) {
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
|
||||
.el-input__wrapper {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
font-family: '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-alert) {
|
||||
height: 90px;
|
||||
padding: 0 20px;
|
||||
margin-bottom: 20px;
|
||||
background-color: rgb(249 249 249 / 49.8%);
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 0;
|
||||
|
||||
.el-alert__content {
|
||||
height: 100%;
|
||||
align-items: baseline;
|
||||
|
||||
.el-alert__title {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
font-family: '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-alert__description {
|
||||
font-family: '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
|
||||
.like {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 20px;
|
||||
font-family: MicrosoftYaHei, '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.el-alert__close-btn {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div class="w-80% m-auto search">
|
||||
<div class="bg-white h-60px p-20px mt-10px flex items-center top b-1px b-solid b-#e9e9e9">
|
||||
<div class="mr-15px">
|
||||
<Icon icon="flowbite:messages-solid" :size="52" class="c-#999999"></Icon>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="h-24px c-#666666"> 欢迎来到内部交流论坛 </div>
|
||||
<div class="h-24px c-#999999 text-12px font-wryh flex items-center">
|
||||
你可以自由选择你感兴趣的话题
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-auto flex top-right">
|
||||
<el-input v-model="inputValue" placeholder="输入搜索关键字" class="mr-20px h-35px" />
|
||||
<Icon icon="ic:outline-search" :size="22" class="cursor-pointer search"></Icon>
|
||||
<el-button type="primary" style="width: 100px; height: 35px" @click="inputVisible = true">
|
||||
<Icon icon="vaadin:plus" :size="18"></Icon>
|
||||
创建话题
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white b-1px b-solid b-#E9E9E9 w-100%">
|
||||
<div
|
||||
class="h-70px flex items-center pl-20px pr-20px"
|
||||
style="border-bottom: 1px solid #e9e9e9"
|
||||
>
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<el-tab-pane label="最新话题" name="first"></el-tab-pane>
|
||||
<el-tab-pane label="热门话题" name="second"></el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="ml-auto text-14px c-#999999" style="font-family: '微软雅黑', sans-serif">
|
||||
总贴数量:1000
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-[calc(100%-60px)] pl-30px pr-30px mt-20px">
|
||||
<LowTable
|
||||
:ref="(el) => (tabRef = el)"
|
||||
tableId="1854716968965484545"
|
||||
:enhanceData="tabEnhanceData"
|
||||
>
|
||||
</LowTable>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="inputVisible"
|
||||
title="创建话题"
|
||||
width="800"
|
||||
:before-close="dialogHandleClose"
|
||||
destroy-on-close
|
||||
>
|
||||
<FormView
|
||||
formType="add"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:showButton="true"
|
||||
:enhanceData="{ updateDialog, type: 'add' }"
|
||||
formId="1854442042421391362"
|
||||
></FormView>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormView, LowTable } from '@/components/LowDesign/index'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
|
||||
const inputValue = ref('')
|
||||
const inputVisible = ref(false)
|
||||
|
||||
const updateDialog = () => {
|
||||
inputVisible.value = false
|
||||
tabRef.value.crudRef.refreshChange()
|
||||
}
|
||||
const dialogHandleClose = (done: () => void) => {
|
||||
done()
|
||||
}
|
||||
const tabEnhanceData = ref({
|
||||
type: 'view'
|
||||
})
|
||||
const tabRef = ref()
|
||||
const activeName = ref('first')
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search {
|
||||
.border-style {
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
}
|
||||
|
||||
.font-wryh {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
}
|
||||
|
||||
.top {
|
||||
margin-bottom: 20px;
|
||||
|
||||
::v-deep(.el-input) {
|
||||
width: 250px;
|
||||
|
||||
.el-input__wrapper {
|
||||
// color: rgb(204, 204, 204);
|
||||
border-radius: 200px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px var(--el-input-focus-border-color) inset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.top-right {
|
||||
position: relative;
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 220px;
|
||||
color: #999;
|
||||
transform: translateY(-50%);
|
||||
|
||||
&:hover {
|
||||
color: #409eff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-tabs) {
|
||||
.el-tabs__header {
|
||||
margin: 0;
|
||||
|
||||
.el-tabs__nav-wrap {
|
||||
&::after {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.el-tabs__nav-scroll {
|
||||
.el-tabs__item {
|
||||
width: 100px;
|
||||
height: 70px;
|
||||
padding: 0;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
|
||||
&.is-active {
|
||||
color: rgb(64 158 255);
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__active-bar {
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-group {
|
||||
::v-deep(.el-radio-button) {
|
||||
.el-radio-button__inner {
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
.el-radio-button__inner {
|
||||
border-radius: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
156
src/views/lowTemplate/generalPage/riendshipForum/index.vue
Normal file
156
src/views/lowTemplate/generalPage/riendshipForum/index.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div class="riendshipForum-box">
|
||||
<div class="bg-white h-60px p-20px mt-10px flex items-center top b-1px b-solid b-#e9e9e9">
|
||||
<div class="mr-15px">
|
||||
<Icon icon="flowbite:messages-solid" :size="52" class="c-#999999"></Icon>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="h-24px c-#666666"> 欢迎来到内部交流论坛 </div>
|
||||
<div class="h-24px c-#999999 text-12px font-wryh flex items-center">
|
||||
你可以自由选择你感兴趣的话题
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-auto flex top-right">
|
||||
<el-input v-model="inputValue" placeholder="输入搜索关键字" class="mr-20px h-35px" />
|
||||
<Icon
|
||||
icon="ic:outline-search"
|
||||
:size="22"
|
||||
class="cursor-pointer search"
|
||||
></Icon>
|
||||
<el-button type="primary" style="width: 100px; height: 35px" @click="inputVisible = true">
|
||||
<Icon icon="vaadin:plus" :size="18"></Icon>
|
||||
创建话题
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white mt-20px b-1px b-solid b-#e9e9e9">
|
||||
<div
|
||||
class="h-70px flex items-center pl-20px pr-20px"
|
||||
style="border-bottom: 1px solid #e9e9e9"
|
||||
>
|
||||
<div
|
||||
class="text-18px font-700 h-19px flex items-center c-#666666 b-l-6px b-r-0 b-t-0 b-b-0 b-#409EFF b-solid pl-10px font-wryh"
|
||||
>
|
||||
讨论板块
|
||||
</div>
|
||||
|
||||
<div class="ml-auto text-14px c-#999999" style="font-family: '微软雅黑', sans-serif">
|
||||
总贴数量:1000
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<LowTable tableId="1854408557228961794" :enhanceData="tabEnhanceData"> </LowTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="inputVisible"
|
||||
title="创建话题"
|
||||
width="800"
|
||||
:before-close="dialogHandleClose"
|
||||
destroy-on-close
|
||||
>
|
||||
<FormView
|
||||
formType="add"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:showButton="true"
|
||||
:enhanceData="{ updateDialog }"
|
||||
formId="1854442042421391362"
|
||||
></FormView>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { LowTable } from '@/components/LowDesign/index'
|
||||
|
||||
defineOptions({ name: 'RiendshipForum' })
|
||||
const inputValue = ref('')
|
||||
|
||||
const tabEnhanceData = ref({
|
||||
type: 'view'
|
||||
})
|
||||
|
||||
const inputVisible = ref(false)
|
||||
|
||||
const updateDialog = () => {
|
||||
inputVisible.value = false
|
||||
}
|
||||
const dialogHandleClose = (done: () => void) => {
|
||||
done()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.riendshipForum-box {
|
||||
width: 1200px;
|
||||
min-height: 500px;
|
||||
margin: 0 auto;
|
||||
// border: 1px solid rgb(233 233 233 / 100%);
|
||||
// box-shadow: 0 0 5px rgb(0 0 0 / 4.71%);
|
||||
|
||||
.font-wryh {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
}
|
||||
|
||||
.top {
|
||||
::v-deep(.el-input) {
|
||||
width: 250px;
|
||||
|
||||
.el-input__wrapper {
|
||||
// color: rgb(204, 204, 204);
|
||||
border-radius: 200px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px var(--el-input-focus-border-color) inset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.top-right {
|
||||
position: relative;
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 220px;
|
||||
color: #999;
|
||||
transform: translateY(-50%);
|
||||
|
||||
&:hover {
|
||||
color: #409eff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-overlay) {
|
||||
.el-dialog {
|
||||
border-radius: 5px;
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 18px 13px 18px 15px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px 5px 0 0;
|
||||
|
||||
.el-dialog__title {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-drawer {
|
||||
background-color: rgb(240 242 245 / 100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1120
src/views/lowTemplate/generalPage/schedule/schedule.vue
Normal file
1120
src/views/lowTemplate/generalPage/schedule/schedule.vue
Normal file
File diff suppressed because it is too large
Load Diff
123
src/views/lowTemplate/generalPage/staffManagement/index.vue
Normal file
123
src/views/lowTemplate/generalPage/staffManagement/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="card-box">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>员工管理</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex" style="min-height: 450px">
|
||||
<div class="flex-basis-200px flex-shrink-0">
|
||||
<avue-tree
|
||||
ref="tree"
|
||||
:option="option"
|
||||
:data="data"
|
||||
v-model="form"
|
||||
@node-expand="nodeExpand"
|
||||
@node-contextmenu="nodeContextmenu"
|
||||
@node-click="nodeClick"
|
||||
>
|
||||
</avue-tree>
|
||||
</div>
|
||||
|
||||
<div class="flex-1" style="max-width: calc(100% - 220px)">
|
||||
<LowTable tableId="1846121741006647298" :enhanceData="{ type: 'view' }"> </LowTable>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { LowTable } from '@/components/LowDesign/index'
|
||||
defineOptions({ name: 'StaffManagement' })
|
||||
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const form = ref({})
|
||||
const data = ref([
|
||||
{
|
||||
value: 0,
|
||||
label: '一级部门',
|
||||
children: [
|
||||
{ value: 1, label: '一级部门1' },
|
||||
{ value: 2, label: '一级部门2' },
|
||||
{ value: 3, label: '一级部门3' },
|
||||
{ value: 4, label: '一级部门4' }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '二级部门',
|
||||
children: [
|
||||
{ value: 4, label: '二级部门1' },
|
||||
{ value: 5, label: '二级部门2' }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
const option = ref({
|
||||
defaultExpandAll: true,
|
||||
filter: false,
|
||||
formOption: {
|
||||
labelWidth: 100,
|
||||
column: [
|
||||
{ label: '自定义项', prop: 'label' },
|
||||
{ label: '测试', prop: 'test' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const tree = ref()
|
||||
|
||||
onMounted(() => {
|
||||
tree.value.setCurrentKey(0)
|
||||
})
|
||||
|
||||
const nodeContextmenu = (data) => {
|
||||
ElMessage.success(JSON.stringify(data))
|
||||
}
|
||||
const nodeExpand = (data) => {
|
||||
ElMessage.success(JSON.stringify(data))
|
||||
}
|
||||
|
||||
const nodeClick = (data) => {
|
||||
ElMessage.success(JSON.stringify(data))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep(.el-card) {
|
||||
&.card-box {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.el-card__header {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
background-color: #f9f9f9;
|
||||
|
||||
// .el-card__body { }
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-tree) {
|
||||
.el-tree-node.is-current > .el-tree-node__content {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.el-tree-node__content {
|
||||
height: 40px;
|
||||
|
||||
.el-tree-node__expand-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.el-tree-node__label {
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
333
src/views/lowTemplate/generalPage/staffManagement/staffView.vue
Normal file
333
src/views/lowTemplate/generalPage/staffManagement/staffView.vue
Normal file
@@ -0,0 +1,333 @@
|
||||
<template>
|
||||
<div class="userManagement-box">
|
||||
<div class="b-#F0F2F5 b-2px b-solid rounded-xl">
|
||||
<FormView
|
||||
formType="view"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="staffViewTopData.defaultData"
|
||||
:showButton="false"
|
||||
:formId="staffViewTopData.formId"
|
||||
></FormView>
|
||||
</div>
|
||||
<div class="mt-20px pb-20px b-#F0F2F5 b-2px b-solid" style="border-radius: 5px">
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<template v-for="item in tabsList" :key="item.labelKey">
|
||||
<el-tab-pane :label="item.label" :name="item.labelKey">
|
||||
<template v-if="item.formId">
|
||||
<div style="padding: 5px 18px">
|
||||
<FormView
|
||||
:formType="item.formType ? item.formType : 'view'"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="item.defaultData"
|
||||
:showButton="true"
|
||||
:formId="item.formId"
|
||||
></FormView>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.tableId">
|
||||
<div style="padding: 5px 18px">
|
||||
<LowTable :tableId="item.tableId" :enhanceData="item.defaultData"> </LowTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="!item.formId && !item.tableId">
|
||||
<div style="padding: 30px 48px">
|
||||
<div class="mb-26px text-14px">
|
||||
<span
|
||||
class="font-bold"
|
||||
style="font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif"
|
||||
>数据权限</span
|
||||
>
|
||||
<span>(设置该角色的用户可以操作的数据的范围)</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-radio-group v-model="radio" class="grid-cols-1" style="display: grid">
|
||||
<el-radio :value="Ritem.value" v-for="Ritem in radioList" :key="Ritem.value">
|
||||
<template #default>
|
||||
<span class="inline-block w-170px text-14px">{{ Ritem.title }}</span>
|
||||
<span class="text-13px c-#999999">{{ Ritem.introduce }} </span>
|
||||
</template>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center mt-80px">
|
||||
<el-button type="primary" style="width: 140px; height: 40px" @click="sjxqCick">
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormView, LowTable } from '@/components/LowDesign/index'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'UserManagement' })
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
// 自定义的翻译文件
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
controlView: {
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const controlView = ref(props.controlView)
|
||||
|
||||
const staffViewTopDefaultData = ref(props.data)
|
||||
staffViewTopDefaultData.value = {
|
||||
...staffViewTopDefaultData.value,
|
||||
avatar_img:
|
||||
'http://oss.yckxt.com/chatgpt/upload/1/2024-10-14/1/f0ee8a3c7c9638a54940382568c9dpng.png'
|
||||
}
|
||||
|
||||
const staffViewTopData = ref({
|
||||
formId: '1846457022071132161',
|
||||
defaultData: staffViewTopDefaultData.value
|
||||
})
|
||||
|
||||
const radio = ref(1)
|
||||
|
||||
interface TableList {
|
||||
labelKey: string
|
||||
label: string
|
||||
formId?: string
|
||||
tableId?: string
|
||||
defaultData: object
|
||||
formType?: 'add' | 'edit' | 'view'
|
||||
showButton?: string
|
||||
}
|
||||
|
||||
let tabsList = ref<TableList[]>([
|
||||
{
|
||||
labelKey: 'dlrz',
|
||||
label: '登录日志',
|
||||
tableId: '1846734665412726786',
|
||||
defaultData: {
|
||||
type: 'view'
|
||||
}
|
||||
},
|
||||
{
|
||||
labelKey: 'czjl',
|
||||
label: '操作记录',
|
||||
tableId: '1846747363458351106',
|
||||
defaultData: {
|
||||
type: 'view'
|
||||
}
|
||||
},
|
||||
{
|
||||
labelKey: 'czqx',
|
||||
label: '操作权限',
|
||||
formId: '1846489623477571585',
|
||||
defaultData: {
|
||||
ref: controlView.value
|
||||
},
|
||||
formType: 'edit',
|
||||
showButton: 'true'
|
||||
},
|
||||
{
|
||||
labelKey: 'sjqx',
|
||||
label: '数据权限',
|
||||
defaultData: {}
|
||||
},
|
||||
{
|
||||
labelKey: 'zdqx',
|
||||
label: '字段权限',
|
||||
formId: '1846758342212648962',
|
||||
defaultData: {
|
||||
ref: controlView.value
|
||||
},
|
||||
formType: 'edit',
|
||||
showButton: 'true'
|
||||
}
|
||||
])
|
||||
|
||||
const activeName = ref('dlrz')
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
const radioList = ref([
|
||||
{ title: '个人', introduce: '只能操作自己和下属的数据', value: 1 },
|
||||
{ title: '所属部门', introduce: '能操作自己、下属、和自己所属部门的数据', value: 2 },
|
||||
{
|
||||
title: '所属部门及下属部门',
|
||||
introduce: '所属部门及下属部门 能操作自己、下属和自己所属部门及其子部门的数据',
|
||||
value: 3
|
||||
},
|
||||
{ title: '全公司', introduce: '能操作全公司的数据', value: 4 }
|
||||
])
|
||||
const sjxqCick = () => {
|
||||
ElMessage({
|
||||
message: '保存成功',
|
||||
type: 'success'
|
||||
})
|
||||
controlView.value!.show = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep(.el-tabs) {
|
||||
.el-tabs__nav-wrap {
|
||||
height: 53px;
|
||||
background-color: #f9f9f9;
|
||||
|
||||
.el-tabs__item {
|
||||
height: 53px;
|
||||
font-weight: bold;
|
||||
line-height: 53px;
|
||||
color: rgb(102 102 102);
|
||||
|
||||
&.is-active {
|
||||
color: rgb(64 158 255);
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__content {
|
||||
.el-radio-group {
|
||||
.el-radio {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.left-bule-text {
|
||||
margin-bottom: 20px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
border-left: 6px solid #409eff;
|
||||
}
|
||||
|
||||
::v-deep(.el-tag) {
|
||||
width: 110px;
|
||||
height: 35px;
|
||||
margin-right: 10px;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
background-color: white;
|
||||
border-color: #ebeef5;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-new-tag {
|
||||
background-color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
::v-deep(.el-overlay) {
|
||||
.el-dialog {
|
||||
border-radius: 5px;
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 13px 13px 13px 15px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px 5px 0 0;
|
||||
|
||||
.el-dialog__title {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
height: 56.8px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
|
||||
.el-checkbox-group {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
|
||||
.el-checkbox {
|
||||
.el-checkbox__inner {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.el-checkbox__label {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding: 12px 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
|
||||
.el-button {
|
||||
width: 70px;
|
||||
height: 28px;
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.el-button--primary {
|
||||
color: white;
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-tabs .el-tabs__nav-wrap {
|
||||
height: 60px;
|
||||
|
||||
.el-tabs__nav {
|
||||
height: 60px;
|
||||
|
||||
.el-tabs__item {
|
||||
height: 60px;
|
||||
// line-height: 60px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #666;
|
||||
|
||||
&.is-active {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
555
src/views/lowTemplate/generalPage/taskManagement/index.vue
Normal file
555
src/views/lowTemplate/generalPage/taskManagement/index.vue
Normal file
@@ -0,0 +1,555 @@
|
||||
<template>
|
||||
<div class="taskManagement-box flex rounded-xl">
|
||||
<div class="flex-shrink-0 flex-basis-180px bg-#F9F9F9 taskManagement-left">
|
||||
<div class="h-50px flex items-center ml-55px mt-20px text-14px c-#999999">
|
||||
<Icon icon="ion:flag-outline" :size="15" class="mr-1px"></Icon>
|
||||
任务分类
|
||||
</div>
|
||||
<el-menu
|
||||
:default-active="menuDefaultActive"
|
||||
class="el-menu-vertical-demo bg-#F9F9F9"
|
||||
@open="menuHandleOpen"
|
||||
@close="menuHandleClose"
|
||||
>
|
||||
<template v-for="item in meunList" :key="item.value">
|
||||
<el-menu-item :index="item.value">
|
||||
<span>{{ item.title }}</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-menu>
|
||||
</div>
|
||||
<div class="flex-1 bg-white p-31px taskManagement-right" style="max-width: calc(100% - 180px)">
|
||||
<div class="mt-10px flex items-center util">
|
||||
<el-radio-group v-model="viewOrListRadio">
|
||||
<el-radio-button label="看板视图" value="view" />
|
||||
<el-radio-button label="列表显示" value="list" />
|
||||
</el-radio-group>
|
||||
|
||||
<el-select v-model="selectValue" :fit-input-width="true">
|
||||
<el-option
|
||||
v-for="item in selectOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button class="ml-auto w-100px" type="primary" @click="dialogDisplay = true">
|
||||
<Icon icon="oi:plus" :size="11"></Icon>
|
||||
<span>创建任务</span>
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="viewOrListRadio == 'view'" class="w-100% grid grid-cols-4 gap-x-15px mt-30px">
|
||||
<div v-for="(LItem, LIndex) in viewList" :key="LIndex">
|
||||
<div class="c-#666666 text-14px mb-15px">
|
||||
<span
|
||||
class="font-700"
|
||||
style="font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif"
|
||||
>
|
||||
{{ LItem.title }}
|
||||
</span>
|
||||
<span class="ml-5px" style="font-family: '微软雅黑', sans-serif">
|
||||
<span v-if="LItem.title == '今日任务'">0/</span>{{ LItem.len }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-for="item in LItem.list" :key="item.id" class="flex card-item">
|
||||
<div class="h-29px flex items-center">
|
||||
<div
|
||||
v-if="ViewVhecked.indexOf(LItem.title + item.id) !== -1"
|
||||
class="check flex items-center justify-center"
|
||||
>
|
||||
<Icon
|
||||
icon="fa-solid:check"
|
||||
:size="11"
|
||||
@click="checkedClick(LItem.title + item.id)"
|
||||
></Icon>
|
||||
</div>
|
||||
<div v-else @click="checkedClick(LItem.title + item.id)" class="nocheck"> </div>
|
||||
</div>
|
||||
<div class="grid grid-rows-3 h-100% ml-10px">
|
||||
<div
|
||||
class="text-14px flex items-center c-#666666 cursor-pointer title-hover"
|
||||
:class="ViewVhecked.indexOf(LItem.title + item.id) !== -1 ? 'line-through' : ''"
|
||||
@click="titleClick(item)"
|
||||
>
|
||||
{{ item.title }},点击查看详情
|
||||
</div>
|
||||
<div class="flex text-12px items-center">
|
||||
<span>
|
||||
{{ item.xmmc }}
|
||||
</span>
|
||||
<div class="c-#CCCCCC flex items-center ml-15px">
|
||||
<Icon icon="mage:message" :size="14"></Icon>
|
||||
<span class="ml-3px">{{ item.lys }}</span>
|
||||
</div>
|
||||
<div class="c-#CCCCCC flex items-center ml-5px">
|
||||
<Icon icon="line-md:link" :size="14"></Icon>
|
||||
<span class="ml-3px">{{ item.ljs }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center" :class="LItem.title == '未完成' ? 'c-#F56C6C' : ''">
|
||||
<Icon icon="icon-park-outline:alarm-clock" :size="14"></Icon>
|
||||
<span class="text-12px ml-3px"> {{ item.jzsjTit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-center flex-1">
|
||||
<el-avatar :size="24" :src="item.tx" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="viewOrListRadio == 'list'" class="mt-20px">
|
||||
<LowTable
|
||||
:ref="(el) => (tableListRef = el)"
|
||||
tableId="1847103675560071169"
|
||||
:enhanceData="{ type: 'view' }"
|
||||
>
|
||||
</LowTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="add-dialog">
|
||||
<el-dialog
|
||||
v-model="dialogDisplay"
|
||||
title="新建任务"
|
||||
width="550"
|
||||
:before-close="dialogHandleClose"
|
||||
>
|
||||
<FormView
|
||||
formType="add"
|
||||
:ref="(el) => (dialogRef = el)"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:showButton="false"
|
||||
formId="1846825131881762817"
|
||||
></FormView>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogDisplay = false">取消</el-button>
|
||||
<el-button type="primary" @click="dialogSuccess"> 保存 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<div class="row-dialog">
|
||||
<el-dialog
|
||||
v-model="rowDialog"
|
||||
title="任务详情"
|
||||
width="900px"
|
||||
:before-close="dialogHandleClose"
|
||||
>
|
||||
<FormView
|
||||
formType="edit"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:showButton="false"
|
||||
:defaultData="rowDefaultData"
|
||||
formId="1850013430948507650"
|
||||
></FormView>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormView, LowTable } from '@/components/LowDesign/index'
|
||||
import * as TableApi from '@/api/design/table'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
defineOptions({ name: 'TaskManagement' })
|
||||
|
||||
const viewOrListRadio = ref('view')
|
||||
const selectValue = ref('default')
|
||||
watch(
|
||||
() => selectValue.value,
|
||||
(val) => {
|
||||
console.log(val, '111')
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => viewOrListRadio.value,
|
||||
(val) => {
|
||||
console.log(val, '111')
|
||||
}
|
||||
)
|
||||
interface Record {
|
||||
fj?: string
|
||||
jzsj?: Date
|
||||
kssj?: Date
|
||||
status?: string
|
||||
xmmc?: string
|
||||
tx?: string
|
||||
title?: string
|
||||
id?: string
|
||||
lys?: string
|
||||
ljs?: string
|
||||
jzsjTit?: string
|
||||
}
|
||||
interface ViewList {
|
||||
title: string
|
||||
list?: Record[]
|
||||
len?: number
|
||||
}
|
||||
|
||||
const viewList = ref<ViewList[]>([])
|
||||
let ViewVhecked = ref<any>([])
|
||||
const checkedClick = (id) => {
|
||||
let index = ViewVhecked.value.indexOf(id)
|
||||
if (index === -1) {
|
||||
ViewVhecked.value.push(id)
|
||||
} else {
|
||||
ViewVhecked.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const meunList = ref([
|
||||
{ value: '1', title: '全部任务' },
|
||||
{ value: '2', title: '我创建的任务' },
|
||||
{ value: '3', title: '我参与的任务' },
|
||||
{ value: '4', title: '下属的任务' },
|
||||
{ value: '5', title: '关注的任务' }
|
||||
])
|
||||
const menuDefaultActive = ref('1')
|
||||
|
||||
const menuHandleOpen = (key: string, keyPath: string[]) => {
|
||||
console.log(key, keyPath)
|
||||
}
|
||||
const menuHandleClose = (key: string, keyPath: string[]) => {
|
||||
console.log(key, keyPath)
|
||||
}
|
||||
|
||||
const selectOptions = [
|
||||
{
|
||||
value: 'default',
|
||||
label: '按默认视图',
|
||||
subItem: [
|
||||
{ title: '今日任务', total: 0, completed: 0 },
|
||||
{ title: '进行中', total: 0, completed: 0 },
|
||||
{ title: '未完成', total: 0, completed: 0 },
|
||||
{ title: '已完成', total: 0, completed: 0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: 'degree',
|
||||
label: '按紧要程度',
|
||||
subItem: [
|
||||
{ title: '重要', total: 0, completed: 0 },
|
||||
{ title: '紧急', total: 0, completed: 0 },
|
||||
{ title: '普通', total: 0, completed: 0 },
|
||||
{ title: '较低', total: 0, completed: 0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: 'endTime',
|
||||
label: '按截止时间',
|
||||
subItem: [
|
||||
{ title: '已逾期', total: 0, completed: 0 },
|
||||
{ title: '今天', total: 0, completed: 0 },
|
||||
{ title: '三天内', total: 0, completed: 0 },
|
||||
{ title: '七天内', total: 0, completed: 0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: 'updateTime',
|
||||
label: '按更新时间',
|
||||
subItem: [
|
||||
{ title: '今天', total: 0, completed: 0 },
|
||||
{ title: '七天内', total: 0, completed: 0 },
|
||||
{ title: '十五天', total: 0, completed: 0 },
|
||||
{ title: '更远', total: 0, completed: 0 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const dialogHandleClose = (done: () => void) => {
|
||||
done()
|
||||
}
|
||||
|
||||
const dialogDisplay = ref(false)
|
||||
const rowDialog = ref(false)
|
||||
const dialogRef = ref()
|
||||
|
||||
const tableListRef = ref()
|
||||
|
||||
const dialogSuccess = async () => {
|
||||
const formRef = dialogRef.value.controlRef
|
||||
const listRef = tableListRef.value.crudRef
|
||||
const data = await formRef.handleSubmit(true)
|
||||
if (data) {
|
||||
listRef.refreshChange()
|
||||
dialogDisplay.value = false
|
||||
}
|
||||
}
|
||||
const rowDefaultData = ref({})
|
||||
const titleClick = (row) => {
|
||||
row.jzsj ? (row.jzsj = formatDate(row.jzsj)) : (row.jzsj = '')
|
||||
row.kssj ? (row.kssj = formatDate(row.kssj)) : (row.kssj = '')
|
||||
rowDefaultData.value = row
|
||||
rowDialog.value = true
|
||||
}
|
||||
onMounted(async () => {
|
||||
let { records } = await TableApi.getTableList(
|
||||
'1847103675560071169',
|
||||
{ pageNo: 1, pageSize: 40 },
|
||||
false
|
||||
)
|
||||
|
||||
const categorizedRecords: { [key: string]: Record[] } = {
|
||||
今日任务: [],
|
||||
进行中: [],
|
||||
未完成: [],
|
||||
已完成: []
|
||||
}
|
||||
records.forEach((ele) => {
|
||||
const ljs = ele.fj ? (ele.fj.includes(',') ? ele.fj.split(',').length : 1) : 0
|
||||
ele.ljs = ljs
|
||||
ele.jzsjTit = ele.jzsj ? `${formatDate(ele.jzsj)} 截止` : ''
|
||||
|
||||
let keys: string[] = []
|
||||
if (ele.kssj && formatDate(ele.kssj).startsWith('2024-10-03')) {
|
||||
keys.push('今日任务')
|
||||
}
|
||||
|
||||
let statusDictionary = [
|
||||
{ id: 1, label: '进行中' },
|
||||
{ id: 2, label: '已完成' },
|
||||
{ id: 3, label: '未完成' }
|
||||
]
|
||||
statusDictionary.forEach((item) => {
|
||||
if (ele.status == item.id) {
|
||||
keys.push(item.label)
|
||||
}
|
||||
})
|
||||
|
||||
keys.forEach((item) => {
|
||||
if (categorizedRecords[item]) {
|
||||
categorizedRecords[item].push(ele)
|
||||
if (item == '已完成') {
|
||||
ViewVhecked.value.push('已完成' + ele.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
for (const [title, list] of Object.entries(categorizedRecords)) {
|
||||
viewList.value.push({
|
||||
title,
|
||||
list,
|
||||
len: list.length
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.taskManagement-box {
|
||||
min-height: 500px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 4.71%);
|
||||
|
||||
.taskManagement-left {
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-right: none;
|
||||
border-radius: 10px 0 0 10px;
|
||||
|
||||
.el-menu {
|
||||
background-color: rgb(255 255 255 / 0%);
|
||||
border-right: none;
|
||||
|
||||
.is-active {
|
||||
font-weight: bold;
|
||||
background-color: #e7f0fa;
|
||||
border-right: 3px solid #409eff;
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
height: 50px;
|
||||
padding-left: 55px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.taskManagement-right {
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 0 10px 10px 0;
|
||||
|
||||
.util {
|
||||
::v-deep(.el-radio-group) {
|
||||
.el-radio-button {
|
||||
.el-radio-button__inner {
|
||||
display: flex;
|
||||
width: 101px;
|
||||
height: 35px;
|
||||
font-family: MicrosoftYaHei, '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
.el-radio-button__inner {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-select) {
|
||||
width: 239px;
|
||||
margin-left: 20px;
|
||||
|
||||
.el-select__wrapper {
|
||||
min-height: 35px;
|
||||
font-size: 14px;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
height: 35px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-item {
|
||||
height: 87px;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
color: #999;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 5px rgb(153 153 153 / 34.9%);
|
||||
|
||||
.title-hover:hover {
|
||||
color: rgb(51 51 51);
|
||||
}
|
||||
|
||||
.check,
|
||||
.nocheck {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.check {
|
||||
color: white;
|
||||
background-color: #409eff;
|
||||
border: 1px solid #409eff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.nocheck {
|
||||
border: 1px solid #ccc;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-dialog {
|
||||
::v-deep(.el-overlay) {
|
||||
.el-dialog {
|
||||
border-radius: 5px;
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 13px 13px 13px 15px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px 5px 0 0;
|
||||
|
||||
.el-dialog__title {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
height: 56.8px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
|
||||
.el-checkbox-group {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
|
||||
.el-checkbox {
|
||||
.el-checkbox__inner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.el-checkbox__label {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding: 12px 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
|
||||
.el-button {
|
||||
width: 70px;
|
||||
height: 28px;
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.el-button--primary {
|
||||
color: white;
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row-dialog {
|
||||
::v-deep(.el-overlay) {
|
||||
.el-dialog__header {
|
||||
height: 24.8px;
|
||||
padding: 16px 10px 16px 16px;
|
||||
font-size: 18px;
|
||||
border-bottom: 1px solid rgb(240 240 240);
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
height: 58.6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-select__placeholder {
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.el-select__popper {
|
||||
.el-select-dropdown__item {
|
||||
font-family: MicrosoftYaHei, '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
|
||||
&.is-selected {
|
||||
font-weight: 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
686
src/views/lowTemplate/generalPage/team/team.vue
Normal file
686
src/views/lowTemplate/generalPage/team/team.vue
Normal file
@@ -0,0 +1,686 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="w-100%" v-if="isShow">
|
||||
<div class="search-box" :class="{'bj':!isTable}">
|
||||
<div class="mt-4 ">
|
||||
<el-input
|
||||
v-model="input"
|
||||
style="width: 900px"
|
||||
clearable
|
||||
placeholder="输入搜索关键字"
|
||||
>
|
||||
<template #append>
|
||||
<div class="search-btn" @click="searchFun(true)">
|
||||
<el-icon><Search /></el-icon>
|
||||
<span style="margin-left: 3px;">搜索</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="fhsyy" v-if="!isTable" @click="isTable=true,is_px=false,input=''">
|
||||
<el-icon><Back /></el-icon>
|
||||
<span style="margin-left: 5px">返回</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table" v-if="isTable">
|
||||
<div class="table-header">
|
||||
<span></span>
|
||||
<span class="c-#666666">知识分类</span>
|
||||
</div>
|
||||
<LowTable :ref="(el)=>(customRef = el)" :tableId="tabbleDate.tableId" :enhanceData="tabbleDate.enhanceData" > </LowTable>
|
||||
</div>
|
||||
<div class="ssjg" v-else>
|
||||
<template v-if="listData.length">
|
||||
<div class="ssjg-top" >
|
||||
<div class="ssjg-top-left">
|
||||
<p>为您找到相关结果约{{total}}个, 搜索用时 (0.23秒)</p>
|
||||
</div>
|
||||
<div class="ssjg-top-right">
|
||||
<span :class="{'color':is_px}" @click="searchFun(true)">默认排序</span>
|
||||
<span :class="{'color':!is_px}" @click="searchFun(false)">按时间排序</span>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="is_show">
|
||||
<div v-loading="loading">
|
||||
<div class="box" v-for="item in listData" :key="item.id">
|
||||
<p class="box-title acitive" @click="viewFun(item)">{{item.title}}</p>
|
||||
<p class="box-text">{{item.text}}</p>
|
||||
<div class="box-lls">
|
||||
<el-icon size="18"><View /></el-icon>
|
||||
<span style="margin: 0 8px;">浏览</span>
|
||||
<span>{{item.llcs}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10,20,30,40,50,100]"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
background
|
||||
layout="prev, pager, next,sizes "
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="null" v-loading="loading">
|
||||
<div>
|
||||
<img src="/img/null.svg" alt=""/>
|
||||
<p class="ts">未查询到相关知识内容</p>
|
||||
<p>建议您修改搜索关键词重新再试</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list" v-else>
|
||||
<div class="list-left">
|
||||
<div class="title">
|
||||
<span></span>
|
||||
<span @click="isShow=true,isTable=true,is_show=true" class="tabs">知识分类</span>
|
||||
<span @click="is_show=true" class="tabs"> > 产品知识</span>
|
||||
<span @click="is_show=false" v-if="!is_show" class="tabs"> > 内容详情 </span>
|
||||
</div>
|
||||
<template v-if="is_show">
|
||||
<div v-loading="loading">
|
||||
<div class="box" v-for="item in listData" :key="item.id">
|
||||
<p class="box-title acitive" @click="viewFun(item)">{{item.title}}</p>
|
||||
<p class="box-text">{{item.text}}</p>
|
||||
<div class="box-lls">
|
||||
<el-icon size="18"><View /></el-icon>
|
||||
<span style="margin: 0 8px;">浏览</span>
|
||||
<span>{{item.llcs}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10,50, 100, 200, 400]"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
background
|
||||
layout="prev, pager, next,sizes "
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="view" v-loading="loading">
|
||||
<div class="top-box">
|
||||
<p>{{objData.title}}</p>
|
||||
<div class="top-box-text">
|
||||
<el-icon size="16"><Clock /></el-icon>
|
||||
<span style="margin: 0 12px 0 5px;">{{objData.time}}</span>
|
||||
<el-icon size="16"><View /></el-icon>
|
||||
<span style="margin: 0 5px;">浏览</span>
|
||||
<span> {{objData.llcs}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="html-text" v-html="objData.content"></div>
|
||||
<div class="tips" v-if="is_fk">
|
||||
<p>以上信息是否对您有帮助?</p>
|
||||
<div>
|
||||
<el-button type="primary" style="padding: 19px 28px;" @click="is_fk=false">有帮助</el-button>
|
||||
<el-button type="primary" style="padding: 19px 28px;" @click="is_fk=false" plain>没帮助</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tips" v-else>
|
||||
<div class="fk">
|
||||
<el-icon size="28px" color="#7ed96d" style="margin-right:10px"><CircleCheck /></el-icon>
|
||||
<span>感谢您的反馈</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="list-right">
|
||||
<header class="header">
|
||||
<p class="header-title">热门知识</p>
|
||||
</header>
|
||||
<template v-for="(item,index) in listData" :key="item.id">
|
||||
<div class="rm" v-if="index < 6">
|
||||
<p class="rm-title acitive" @click="viewFun(item)">{{item.title}}</p>
|
||||
<p class="rm-text">{{item.text}}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Search,View,Clock,CircleCheck,Back } from '@element-plus/icons-vue'
|
||||
import {getTableList,updateTableData} from '@/api/design/table/index'
|
||||
import type { ComponentSize } from 'element-plus'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
const isShow = ref<any>(true)
|
||||
const input = ref('')
|
||||
const customRef = ref()
|
||||
const size = ref<ComponentSize>('default')
|
||||
const loading = ref(false)
|
||||
const is_show = ref(true)
|
||||
const is_fk = ref(true)
|
||||
const isTable = ref(true)
|
||||
const is_px = ref(true)
|
||||
//知识分类点击
|
||||
const clickFun = () =>{
|
||||
isShow.value = false
|
||||
initDate({ pageNo: 1 , pageSize: 10 })
|
||||
|
||||
}
|
||||
|
||||
const tabbleDate = ref({
|
||||
label:'知识分类',
|
||||
type:'table',
|
||||
defaultData:{},
|
||||
tableId:'1854438272522235905',
|
||||
enhanceData: { hideHeader: 'disabled',clickFun}
|
||||
})
|
||||
|
||||
//搜索
|
||||
const searchFun = (type) =>{
|
||||
isTable.value = false
|
||||
is_px.value = type
|
||||
initDate(type ? { pageNo: 1 , pageSize: 10,title:input.value } : { pageNo: 1 , pageSize: 10,column: "create_time",order: "asc"})
|
||||
}
|
||||
const listData = ref<any>([])
|
||||
const pageSize = ref(10)
|
||||
const total = ref()
|
||||
//初始化数据
|
||||
const initDate = (data) =>{
|
||||
new Promise(async (resolve) => {
|
||||
loading.value = true
|
||||
await getTableList('1854438272522235905',data,false).then((res)=>{
|
||||
listData.value = res.records || []
|
||||
total.value = res.total
|
||||
loading.value = false
|
||||
resolve(res.records)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//分页
|
||||
const currentPage = ref(1)
|
||||
const disabled = ref(false)
|
||||
const handleSizeChange = (val: number) => {
|
||||
pageSize.value = val
|
||||
initDate({ pageNo: 1 , pageSize: val })
|
||||
|
||||
}
|
||||
const handleCurrentChange = (val: number) => {
|
||||
initDate({ pageNo: val , pageSize: 10 })
|
||||
}
|
||||
|
||||
//详情
|
||||
const objData = ref()
|
||||
const viewFun = (data) =>{
|
||||
loading.value = true
|
||||
isShow.value = false
|
||||
is_show.value = false
|
||||
data.time = formatDate(data.create_time,'YYYY-MM-DD HH:mm:ss')
|
||||
data.llcs = ++data.llcs
|
||||
objData.value = data
|
||||
updateTableData('1854438272522235905',data)
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-box{
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 180px;
|
||||
background: #FFF;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
::v-deep .el-input-group__append {
|
||||
padding: 0;
|
||||
|
||||
.search-btn{
|
||||
display: flex;
|
||||
padding: 4px 19px;
|
||||
font-family: "微软雅黑", sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
background: #409eff;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.fhsyy{
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 20px;
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fhsyy:hover{
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.table{
|
||||
width:100%;
|
||||
margin-top: 20px;
|
||||
|
||||
.table-header span:nth-child(1){
|
||||
display: inline-block;
|
||||
width: 5.5px;
|
||||
height: 17px;
|
||||
margin-right: 7px;
|
||||
background: #409eff;
|
||||
}
|
||||
|
||||
.table-header{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.ssjg{
|
||||
padding: 32px;
|
||||
background: #fff;
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-top: 0;
|
||||
|
||||
.ssjg-top{
|
||||
display: flex;
|
||||
padding: 6px 0;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 33px;
|
||||
|
||||
.ssjg-top-left{
|
||||
|
||||
p{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.ssjg-top-right{
|
||||
|
||||
span{
|
||||
display: inline-block;
|
||||
padding: 5.5px 22px;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
letter-spacing: normal;
|
||||
color: #666;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.null{
|
||||
display: flex;
|
||||
min-height: 410px;
|
||||
background: #fff;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
p{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ts{
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important;
|
||||
font-size: 16px !important;
|
||||
font-weight: 700 !important;
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.list{
|
||||
display: flex;
|
||||
gap: 25px;
|
||||
|
||||
.list-left{
|
||||
padding: 40px;
|
||||
flex: 8;
|
||||
background: #fff;
|
||||
|
||||
.title span:nth-child(1){
|
||||
display: inline-block;
|
||||
width: 5.2px;
|
||||
height: 16px;
|
||||
margin-right: 5px;
|
||||
background: #409eff;
|
||||
}
|
||||
|
||||
.title{
|
||||
display: flex;
|
||||
margin-bottom: 30px;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// .box{
|
||||
// position: relative;
|
||||
// width: 100%;
|
||||
// padding-bottom: 20px ;
|
||||
// border-bottom: 1px solid rgb(242 242 242 / 100%);
|
||||
|
||||
// p{
|
||||
// padding: 0;
|
||||
// margin: 0;
|
||||
// }
|
||||
|
||||
// .box-title{
|
||||
// font-family: "微软雅黑 Bold", "微软雅黑 Regular", "微软雅黑", sans-serif;
|
||||
// font-size: 18px;
|
||||
// font-style: normal;
|
||||
// font-weight: 700;
|
||||
// line-height: 30px;
|
||||
// color:#666;
|
||||
// cursor: pointer;
|
||||
// }
|
||||
|
||||
// .box-text{
|
||||
// font-family: MicrosoftYaHei, '微软雅黑', sans-serif;
|
||||
// font-size: 14px;
|
||||
// font-style: normal;
|
||||
// font-weight: 400;
|
||||
// line-height: 30px;
|
||||
// color: #999;
|
||||
// text-align: left;
|
||||
// }
|
||||
|
||||
// .box-lls{
|
||||
// position: absolute;
|
||||
// right: 50px;
|
||||
// bottom: 50px;
|
||||
// display: flex;
|
||||
// font-family: '微软雅黑', sans-serif;
|
||||
// font-size: 12px;
|
||||
// font-weight: 400;
|
||||
// color: #999;
|
||||
// align-items: center;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .page{
|
||||
// display: flex;
|
||||
// padding: 20px;
|
||||
// justify-content: center;
|
||||
// }
|
||||
|
||||
.view{
|
||||
.top-box{
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid rgb(242 242 242 / 100%);
|
||||
|
||||
p{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
line-height: 36px;
|
||||
color: #666;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.top-box-text{
|
||||
display: flex;
|
||||
font-family: MicrosoftYaHei, '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 36px;
|
||||
color: #999;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.html-text{
|
||||
font-family: MicrosoftYaHei, '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tips{
|
||||
display: flex;
|
||||
padding: 19px;
|
||||
background:#f9f9f9;
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
p{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 36px;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.fk{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span{
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 36px;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-right{
|
||||
flex: 2;
|
||||
background: #fff;
|
||||
|
||||
.header{
|
||||
display: flex;
|
||||
height: 50px;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid rgb(233 233 233 / 100%);
|
||||
|
||||
.header-title{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.rm{
|
||||
padding: 15px 0;
|
||||
margin: 0 20px;
|
||||
border-bottom: 1px solid rgb(233 233 233 / 100%);
|
||||
|
||||
p{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rm-title{
|
||||
font-family: "微软雅黑 Bold", "微软雅黑 Regular", "微软雅黑", sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
cursor: pointer;
|
||||
visibility: inherit;
|
||||
|
||||
}
|
||||
|
||||
.rm-text{
|
||||
font-family: MicrosoftYaHei, '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
text-transform: none;
|
||||
word-wrap: break-word;
|
||||
visibility: inherit;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.box{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 10px ;
|
||||
border-bottom: 1px solid rgb(242 242 242 / 100%);
|
||||
|
||||
p{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.box-title{
|
||||
display: inline-block;
|
||||
font-family: "微软雅黑 Bold", "微软雅黑 Regular", "微软雅黑", sans-serif;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 30px;
|
||||
color:#666;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.box-text{
|
||||
font-family: MicrosoftYaHei, '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 30px;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.box-lls{
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
bottom: 50px;
|
||||
display: flex;
|
||||
font-family: '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #999;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.page{
|
||||
display: flex;
|
||||
padding: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.acitive:hover{
|
||||
color: #409eff !important;
|
||||
}
|
||||
|
||||
.tabs:hover{
|
||||
color: #409eff !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bj{
|
||||
background: #f9f9f9 !important;
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
}
|
||||
|
||||
.color{
|
||||
color: #fff !important;
|
||||
background: #409eff;
|
||||
}
|
||||
|
||||
::v-deep .el-card {
|
||||
background-color: rgba($color: #000, $alpha: 0%) !important;
|
||||
}
|
||||
|
||||
::v-deep .el-input__wrapper{
|
||||
border-right:0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
box-shadow: 0 0 0 1px #409eff inset;
|
||||
}
|
||||
</style>
|
||||
343
src/views/lowTemplate/generalPage/teamDetails/index.vue
Normal file
343
src/views/lowTemplate/generalPage/teamDetails/index.vue
Normal file
@@ -0,0 +1,343 @@
|
||||
<template>
|
||||
<div class="TeamDetails-box rounded-xl">
|
||||
<div class="flex border-box p-20px h-110px items-center">
|
||||
<div class="mr-15px">
|
||||
<el-avatar class="avatar" :size="60" :src="PropsData.tx" />
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div>
|
||||
<div class="text-18px font-700 c-#666666 font-wryh h-28px flex items-center">
|
||||
{{ PropsData.name }}
|
||||
</div>
|
||||
<div class="text-14px c-#999999 flex items-center w-90%" style="line-height: 28px">
|
||||
{{ PropsData.tdjs }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<el-button type="primary" @click="dialogBjtd = true">
|
||||
<Icon icon="fa-solid:pen" class="mr-3px"></Icon>
|
||||
编辑团队
|
||||
</el-button>
|
||||
<el-button @click="dialogDel = true">
|
||||
<Icon icon="ic:sharp-delete" class="mr-3px"></Icon>
|
||||
删除团队
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-100% mt-20px items-start">
|
||||
<!-- -->
|
||||
<div class="flex-shrink-0 w-70% mr-10px border-box p-20px">
|
||||
<div class="h-60px flex items-center">
|
||||
<div
|
||||
class="font-wryh font-700 c-#666666 text-14px pl-10px"
|
||||
style="border-left: 6px solid #409eff"
|
||||
>
|
||||
数据统计
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LowTable tableId="1852234473116233730" :enhanceData="{ type: 'view' }"> </LowTable>
|
||||
<div class="h-60px flex items-center">
|
||||
<div
|
||||
class="font-wryh font-700 c-#666666 text-14px pl-10px"
|
||||
style="border-left: 6px solid #409eff"
|
||||
>
|
||||
团队动态
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in tddtList"
|
||||
:key="index"
|
||||
class="flex pl-20px h-70px items-center"
|
||||
style="border-bottom: 1px solid rgb(233 233 233 / 100%)"
|
||||
>
|
||||
<div class="mr-20px">
|
||||
<el-avatar v-if="item.avatar" class="avatar" :size="35" :src="item.avatar" />
|
||||
<el-avatar
|
||||
v-else
|
||||
class="avatar"
|
||||
:size="35"
|
||||
src="https://oss.yckxt.com/chatgpt/upload/1/2024-10-30/1/f0ee8a3c7c9638a54940382568c9dpng_3.png"
|
||||
/>
|
||||
</div>
|
||||
<div style="font-family: MicrosoftYaHei, '微软雅黑 Regular', '微软雅黑', sans-serif">
|
||||
<div claas="h-24px flex items-center">
|
||||
<span class="mr-5px"> {{ item.xm }} </span>
|
||||
<template v-if="item.sjType == 0">
|
||||
<span class="mr-5px c-#409EFF"> {{ item.blueText }} </span>
|
||||
<span class="c-#666666"> {{ item.text }} </span>
|
||||
</template>
|
||||
<template v-else-if="item.sjType == 1">
|
||||
<span class="mr-5px c-#666666"> {{ item.text }}</span>
|
||||
<span class="c-#409EFF"> {{ item.blueText }} </span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-12px h-24px flex items-center c-#999999">几秒前</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-100px pb-10px flex items-center justify-center ">
|
||||
<el-button round class="w-120px" style="height: 35px;">查看更多</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-25% ml-10px cylb border-box">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="flex h-60px items-center justify-between pl-20px pr-20px bg-#F9F9F9">
|
||||
<div class="text-16px c-#666666 font-wryh font-700"> 成员列表 </div>
|
||||
<div>
|
||||
<el-button-group @click="dialogDel = true">
|
||||
<el-button style="width: 50px; height: 35px">
|
||||
<Icon icon="formkit:left" class="c-#999999"></Icon>
|
||||
</el-button>
|
||||
<el-button style="width: 50px; height: 35px">
|
||||
<Icon icon="formkit:right" class="c-#999999"></Icon>
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-for="(item, index) in cylbList"
|
||||
:key="index"
|
||||
class="h-70px flex items-center pl-20px pr-20px"
|
||||
style="border-bottom: 1px solid rgb(242 242 242 / 100%)"
|
||||
>
|
||||
<el-avatar class="avatar" :size="35" :src="item" />
|
||||
|
||||
<span class="ml-10px c-#666666 font-700 font-wryh">小明</span>
|
||||
<span class="ml-10px c-#999999 font-wryh">销售总监</span>
|
||||
<Icon
|
||||
class="ml-auto c-#999999 cursor-pointer"
|
||||
icon="mingcute:delete-line"
|
||||
@click="iconClick"
|
||||
></Icon>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-bjtd">
|
||||
<el-dialog v-model="dialogBjtd" title="创建团队" width="720px" :before-close="handleClose">
|
||||
<FormView
|
||||
formType="add"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:showButton="false"
|
||||
:defaultData="PropsData"
|
||||
formId="1852168337334951938"
|
||||
></FormView>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogBjtd = false">取消</el-button>
|
||||
<el-button type="primary" @click="dialogBjtdClick"> 保存 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<div class="dialog-czqr">
|
||||
<el-dialog v-model="dialogDel" title="操作确认" width="450" :before-close="handleClose">
|
||||
<div class="flex p-20px h-100px">
|
||||
<div class="w-50px h-50px flex justify-center items-center">
|
||||
<el-avatar
|
||||
class="bg-white"
|
||||
:size="27"
|
||||
src="http://oss.yckxt.com/chatgpt/upload/1/2024-10-14/1/mdi--question-mark-circle-outline (1).png"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-15px">
|
||||
<div class="text-16px c-#666666 font-700 h-28px">是否确定删除数据?</div>
|
||||
<div class="c-#999999 h-28px">是否确定删除数据?</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogDel = false">取消</el-button>
|
||||
<el-button type="primary" @click="dialogDelClick"> 确定 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormView, LowTable } from '@/components/LowDesign/index'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'TeamDetails' })
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
// 自定义的翻译文件
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
const PropsData = props.data
|
||||
const cylbList = ref([])
|
||||
cylbList.value = PropsData.cytx.split(',')
|
||||
|
||||
const dialogBjtd = ref(false)
|
||||
const dialogBjtdClick = () => {
|
||||
dialogBjtd.value = false
|
||||
ElMessage({
|
||||
message: '保存成功',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
const handleClose = (done: () => void) => {
|
||||
done()
|
||||
}
|
||||
|
||||
const dialogDel = ref(false)
|
||||
|
||||
const iconClick = () => {
|
||||
dialogDel.value = true
|
||||
}
|
||||
|
||||
const dialogDelClick = () => {
|
||||
dialogDel.value = false
|
||||
ElMessage({
|
||||
message: '删除成功',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
const tddtList = ref([
|
||||
{
|
||||
avatar:
|
||||
'https://oss.yckxt.com/chatgpt/upload/1/2024-10-30/1/f0ee8a3c7c9638a54940382568c9dpng_3.png',
|
||||
xm: '赵小刚',
|
||||
blueText: '将 5 月日常迭代',
|
||||
text: '更新至已发布状态',
|
||||
fbTime: '几秒前',
|
||||
sjType: 0
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
'https://oss.yckxt.com/chatgpt/upload/1/2024-10-30/1/f0ee8a3c7c9638a54940382568c9dpng_3.png',
|
||||
xm: '赵小刚',
|
||||
blueText: '将 5 月日常迭代',
|
||||
text: '更新至已发布状态',
|
||||
fbTime: '几秒前',
|
||||
sjType: 1
|
||||
},
|
||||
{
|
||||
avatar: 'http://oss.yckxt.com/chatgpt/upload/1/2024-10-30/1/formkit--people.png',
|
||||
xm: '赵小刚',
|
||||
blueText: '将 5 月日常迭代',
|
||||
text: '更新至已发布状态',
|
||||
fbTime: '几秒前',
|
||||
sjType: 0
|
||||
},
|
||||
{
|
||||
avatar: '',
|
||||
xm: '赵小刚',
|
||||
blueText: '将 5 月日常迭代',
|
||||
text: '更新至已发布状态',
|
||||
fbTime: '几秒前',
|
||||
sjType: 1
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.TeamDetails-box {
|
||||
min-height: 500px;
|
||||
|
||||
.font-wryh {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
}
|
||||
|
||||
.border-box {
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 4.71%);
|
||||
|
||||
.avatar {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.cylb {
|
||||
::v-deep(.el-card) {
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
box-shadow: none;
|
||||
|
||||
.el-card__header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-bjtd,
|
||||
.dialog-czqr {
|
||||
::v-deep(.el-overlay) {
|
||||
.el-dialog {
|
||||
border-radius: 5px;
|
||||
|
||||
.el-dialog__header {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
background-color: rgb(245 245 245 / 100%);
|
||||
border-radius: 5px 5px 0 0;
|
||||
box-shadow: 0 1px 1px rgb(233 233 233 / 100%);
|
||||
align-items: center;
|
||||
|
||||
.el-dialog__title {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.el-icon {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
height: 60px;
|
||||
padding: 15px 20px;
|
||||
box-shadow: 0 1px 1px 0 rgb(233 233 233 / 100%) inset;
|
||||
|
||||
.el-button {
|
||||
width: 80px;
|
||||
height: 30px;
|
||||
padding: 5px 15px;
|
||||
font-family: MicrosoftYaHei, '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-czqr {
|
||||
::v-deep(.el-overlay) {
|
||||
.el-dialog__body {
|
||||
.el-avatar {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
394
src/views/lowTemplate/generalPage/userManagement/index.vue
Normal file
394
src/views/lowTemplate/generalPage/userManagement/index.vue
Normal file
@@ -0,0 +1,394 @@
|
||||
<template>
|
||||
<div class="userManagement-box">
|
||||
<div class="b-#F0F2F5 b-2px b-solid rounded-xl">
|
||||
<FormView
|
||||
formType="view"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="userViewTopData.defaultData"
|
||||
:showButton="false"
|
||||
:formId="userViewTopData.formId"
|
||||
></FormView>
|
||||
</div>
|
||||
<div class="mt-20px pb-20px b-#F0F2F5 b-2px b-solid" style="border-radius: 5px">
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<template v-for="item in tabsList" :key="item.labelKey">
|
||||
<el-tab-pane :label="item.label" :name="item.labelKey">
|
||||
<template v-if="item.lowDesign.type == 'design' && item.lowDesign.formId">
|
||||
<FormView
|
||||
formType="view"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="item.lowDesign?.defaultData"
|
||||
:showButton="false"
|
||||
:formId="item.lowDesign.formId"
|
||||
></FormView>
|
||||
</template>
|
||||
|
||||
<template v-if="item.lowDesign.type == 'exploit' && item.lowDesign.tableId">
|
||||
<div style="padding: 5px 18px">
|
||||
<LowTable
|
||||
:tableId="item.lowDesign.tableId"
|
||||
:enhanceData="item.lowDesign?.defaultData"
|
||||
>
|
||||
</LowTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="item.lowDesign.type == 'designAndExploit' && item.lowDesign.formId">
|
||||
<FormView
|
||||
formType="view"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="item.lowDesign?.defaultData"
|
||||
:showButton="false"
|
||||
:formId="item.lowDesign.formId"
|
||||
></FormView>
|
||||
<div class="ml-28px mr-28px">
|
||||
<div class="mt-22px font-700 text-14px c-#666666 left-bule-text">
|
||||
<span class="pl-8px">用户标签</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-tag
|
||||
v-for="tag in dynamicTags"
|
||||
:key="tag"
|
||||
closable
|
||||
:disable-transitions="false"
|
||||
@close="tagHandleClose(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
v-if="!inputVisible"
|
||||
class="button-new-tag"
|
||||
@click="inputVisible = true"
|
||||
>
|
||||
<Icon icon="ic:twotone-plus" class="text-#409EFF"></Icon>
|
||||
<span class="text-#409EFF text-14px">添加标签</span>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="mt-38px font-700 text-14px c-#666666 left-bule-text">
|
||||
<span class="pl-8px">收货地址</span>
|
||||
</div>
|
||||
<div>
|
||||
<LowTable tableId="1846073998846337026" :enhanceData="{ type: 'view' }">
|
||||
</LowTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="inputVisible"
|
||||
title="标签设置"
|
||||
width="600"
|
||||
:before-close="dialogHandleClose"
|
||||
destroy-on-close
|
||||
>
|
||||
<FormView
|
||||
formType="add"
|
||||
:ref="(el) => (yhxqRef = el)"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:showButton="false"
|
||||
formId="1845041920745218050"
|
||||
></FormView>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="inputVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dialogSuccess"> 确认 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormView, LowTable } from '@/components/LowDesign/index'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'UserManagement' })
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
// 自定义的翻译文件
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
const userViewTopDefaultData = ref(props.data)
|
||||
userViewTopDefaultData.value = {
|
||||
...userViewTopDefaultData.value,
|
||||
avatar_img:
|
||||
'http://oss.yckxt.com/chatgpt/upload/1/2024-10-14/1/f0ee8a3c7c9638a54940382568c9dpng.png'
|
||||
}
|
||||
|
||||
const userViewTopData = ref({
|
||||
formId: '1845673651634458625',
|
||||
defaultData: userViewTopDefaultData.value
|
||||
})
|
||||
|
||||
let tabsList = ref([
|
||||
{
|
||||
labelKey: 'yhxq',
|
||||
label: '用户详情',
|
||||
lowDesign: {
|
||||
formId: '1845757477997731842',
|
||||
type: 'designAndExploit',
|
||||
defaultData: {
|
||||
...props.data,
|
||||
cs: '220000,220100,220102',
|
||||
czz: '100',
|
||||
dlcs: '100',
|
||||
fields_8038839: '统计信息',
|
||||
fields_9105030: '用户资料',
|
||||
fs: '100',
|
||||
gxqm: '一个有个性的男子',
|
||||
gz: '100',
|
||||
id: 'id',
|
||||
scht: '100',
|
||||
scpj: '100',
|
||||
scsp: '100',
|
||||
sczt: '100',
|
||||
sjhm: '18088889999',
|
||||
sppj: '100',
|
||||
sr: '2016-10-18',
|
||||
sycjcs: '100',
|
||||
thjl: '100',
|
||||
xb: '1',
|
||||
xhdfl: '服装、餐厨',
|
||||
yhj: '100',
|
||||
yqhy: '100',
|
||||
zhdl: '2024-08-22 22:10:00',
|
||||
zy: '学生'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
labelKey: 'ddjl',
|
||||
label: '订单记录',
|
||||
lowDesign: {
|
||||
tableId: '1844275960258019330',
|
||||
type: 'exploit',
|
||||
defaultData: {
|
||||
type: 'view'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
labelKey: 'jfjl',
|
||||
label: '积分记录',
|
||||
lowDesign: {
|
||||
tableId: '1846095079682482177',
|
||||
type: 'exploit',
|
||||
defaultData: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
labelKey: 'dlrz',
|
||||
label: '登录日志',
|
||||
lowDesign: {
|
||||
tableId: '1846100518918823938',
|
||||
type: 'exploit',
|
||||
defaultData: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
labelKey: 'czjl',
|
||||
label: '操作记录',
|
||||
lowDesign: {
|
||||
tableId: '1846103236139950082',
|
||||
type: 'exploit',
|
||||
defaultData: {}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const activeName = ref('yhxq')
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
|
||||
const dynamicTags = ref(['标签名称一', '标签名称二', '标签名称三', '标签名称四'])
|
||||
const inputVisible = ref(false)
|
||||
const yhxqRef = ref()
|
||||
|
||||
const tagHandleClose = (tag: string) => {
|
||||
dynamicTags.value.splice(dynamicTags.value.indexOf(tag), 1)
|
||||
}
|
||||
|
||||
const dialogHandleClose = (done: () => void) => {
|
||||
done()
|
||||
}
|
||||
|
||||
const dialogSuccess = async () => {
|
||||
const formRef = yhxqRef.value.controlRef
|
||||
const data = await formRef.handleSubmit(true)
|
||||
let bq = data.$tjbq
|
||||
if (bq) {
|
||||
if (bq.indexOf('|') == -1) {
|
||||
dynamicTags.value.push(bq)
|
||||
} else {
|
||||
let bqList = bq.split('|')
|
||||
bqList.forEach((ele) => {
|
||||
dynamicTags.value.push(ele)
|
||||
})
|
||||
}
|
||||
}
|
||||
inputVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep(.el-tabs) {
|
||||
.el-tabs__nav-wrap {
|
||||
height: 53px;
|
||||
background-color: #f9f9f9;
|
||||
|
||||
.el-tabs__item {
|
||||
height: 53px;
|
||||
font-weight: bold;
|
||||
line-height: 53px;
|
||||
color: rgb(102 102 102);
|
||||
|
||||
&.is-active {
|
||||
color: rgb(64 158 255);
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.left-bule-text {
|
||||
margin-bottom: 20px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
border-left: 6px solid #409eff;
|
||||
}
|
||||
|
||||
::v-deep(.el-tag) {
|
||||
width: 120px;
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
background-color: white;
|
||||
border-color: #ebeef5;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-new-tag {
|
||||
height: 40px;
|
||||
background-color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
::v-deep(.el-overlay) {
|
||||
.el-dialog {
|
||||
border-radius: 5px;
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 13px 13px 13px 15px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px 5px 0 0;
|
||||
|
||||
.el-dialog__title {
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
height: 56.8px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
|
||||
.el-checkbox-group {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
|
||||
.el-checkbox {
|
||||
.el-checkbox__inner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.el-checkbox__label {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
display: flex;
|
||||
height: 60px;
|
||||
padding: 10px 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
align-items: center;
|
||||
justify-content: right;
|
||||
|
||||
.el-button {
|
||||
width: 80px;
|
||||
height: 30px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.el-button--primary {
|
||||
color: white;
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-tabs .el-tabs__nav-wrap {
|
||||
height: 60px;
|
||||
|
||||
.el-tabs__nav {
|
||||
height: 60px;
|
||||
|
||||
.el-tabs__item {
|
||||
height: 60px;
|
||||
// line-height: 60px;
|
||||
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
color: #666;
|
||||
|
||||
&.is-active {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-alert v-for="item in defineData" :key="item.id" type="info">
|
||||
<template #title>
|
||||
<el-avatar :size="28" class="mr-5px" :src="item.imgUrl" />
|
||||
<span class="c-#666666 mr-10px">{{ item.name }}</span>
|
||||
<span class="c-#999999 text-12px">{{ item.time }}</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<span class="c-#999999">{{ item.content }}</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div class="flex h-60px mb-10px bottom">
|
||||
<el-input type="text" placeholder="请输入批阅内容" v-model="inputVal" />
|
||||
<el-button type="primary" @click="fbBtn">发布</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
interface DefineData {
|
||||
id: number
|
||||
imgUrl: string
|
||||
name: string
|
||||
time: string
|
||||
content: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data?: DefineData[]
|
||||
}>()
|
||||
|
||||
const defineData = ref(props.data)
|
||||
const inputVal = ref('')
|
||||
let dataLength = ref(0)
|
||||
const fbBtn = () => {
|
||||
if (!inputVal.value) return
|
||||
dataLength.value = defineData.value?.length || 0
|
||||
let endVal = defineData.value![dataLength.value - 1]
|
||||
let id = endVal.id
|
||||
defineData.value![dataLength.value] = {
|
||||
id: id,
|
||||
imgUrl:
|
||||
'https://oss.yckxt.com/chatgpt/upload/1/2024-11-06/1/f0ee8a3c7c9638a54940382568c9dpng_5.png',
|
||||
name: '赵小刚',
|
||||
time: formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss'),
|
||||
content: inputVal.value
|
||||
}
|
||||
inputVal.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep(.el-alert) {
|
||||
height: 90px;
|
||||
margin-bottom: 20px;
|
||||
background-color: rgb(249 249 249 / 49.8%);
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 0;
|
||||
|
||||
.el-alert__content {
|
||||
height: 72px;
|
||||
align-items: baseline;
|
||||
|
||||
.el-alert__title {
|
||||
display: flex;
|
||||
font-family: '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.el-alert__description {
|
||||
font-family: '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.el-alert__close-btn {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
::v-deep(.el-input) {
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
|
||||
.el-input__wrapper {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
font-family: '微软雅黑 Regular', '微软雅黑', sans-serif;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="flex items-start">
|
||||
<div class="w-30% mr-20px border-style">
|
||||
<FormView
|
||||
formType="edit"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:defaultData="form1Data"
|
||||
:showButton="false"
|
||||
formId="1853701496069386242"
|
||||
></FormView>
|
||||
</div>
|
||||
<div class="w-[calc(70%-20px)] border-style">
|
||||
<FormView
|
||||
formType="edit"
|
||||
handleType="returnData"
|
||||
showType="view"
|
||||
:enhanceData="form2EData"
|
||||
:defaultData="form2Data"
|
||||
:showButton="false"
|
||||
formId="1853994635845922818"
|
||||
></FormView>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormView } from '@/components/LowDesign/index'
|
||||
|
||||
defineOptions({ name: 'WorkReportView' })
|
||||
|
||||
const prop = defineProps({
|
||||
data: Object,
|
||||
stepList: Object,
|
||||
form2Data: Object
|
||||
})
|
||||
|
||||
const form1Data = computed(() => {
|
||||
return {
|
||||
...prop.data,
|
||||
stepList: prop.stepList
|
||||
}
|
||||
})
|
||||
const form2EData = ref({})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.border-style {
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 4.71%);
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user