Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev
commit
c3b210d2b5
|
@ -30,6 +30,6 @@ export const KeFuConversationApi = {
|
|||
},
|
||||
// 删除客服会话
|
||||
deleteConversation: async (id: number) => {
|
||||
return await request.get({ url: '/promotion/kefu-conversation/delete?id' + id })
|
||||
return await request.delete({ url: `/promotion/kefu-conversation/delete?id=${id}`})
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.4 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 10 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724297262365" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1396" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M707.91 103c16.28 0 29.522 13.007 29.897 29.195l0.009 0.706v111.878a29.96 29.96 0 0 1-0.898 7.3l171.177-0.001c16.28 0 29.522 13.007 29.897 29.195l0.008 0.706v637.12c0 16.278-13.01 29.518-29.2 29.893l-0.705 0.008H270.884c-16.28 0-29.522-13.007-29.897-29.195l-0.008-0.706V787.274c0-16.514 13.389-29.9 29.905-29.9 16.28 0 29.522 13.007 29.897 29.194l0.008 0.706v101.924h577.4V311.88h-577.4v88.787c0 16.278-13.009 29.518-29.2 29.893l-0.705 0.008c-16.28 0-29.522-13.008-29.897-29.195l-0.008-0.706V281.979c0-16.278 13.009-29.518 29.2-29.893l0.705-0.008h408.019a29.916 29.916 0 0 1-0.89-6.593l-0.008-0.706v-81.978H132.808v407.113h385.787L408.223 456.982c-11.36-11.624-11.329-30.143-0.066-41.729l0.554-0.555c11.625-11.358 30.147-11.327 41.734-0.066l0.555 0.554 161.028 164.762c11.244 11.504 11.344 29.793 0.362 41.42l-0.55 0.565-161.027 161.849c-11.648 11.707-30.583 11.757-42.292 0.11-11.524-11.461-11.754-29.979-0.657-41.723l0.546-0.563 111.319-111.89H102.905c-16.28 0-29.522-13.007-29.897-29.195l-0.008-0.705V132.9c0-16.278 13.01-29.518 29.2-29.893l0.705-0.008H707.91z" p-id="1397"></path></svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -165,6 +165,7 @@ $toolbar-position: -55px;
|
|||
width: 80px;
|
||||
height: 25px;
|
||||
font-size: 12px;
|
||||
color: #6a6a6a;
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
class="mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px"
|
||||
>
|
||||
<!-- 操作按钮区 -->
|
||||
<div class="m--8px m-b-4px flex flex-row items-center justify-between bg-gray-1 p-8px">
|
||||
<div class="m--8px m-b-4px flex flex-row items-center justify-between p-8px" style="background-color: var(--app-content-bg-color);">
|
||||
<el-tooltip content="拖动排序">
|
||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
|
||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" style="color: #8a909c;" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除">
|
||||
<Icon
|
||||
|
|
|
@ -7,26 +7,41 @@ const props = defineProps({
|
|||
src: propTypes.string.def('')
|
||||
})
|
||||
const loading = ref(true)
|
||||
const height = ref('')
|
||||
const frameRef = ref<HTMLElement | null>(null)
|
||||
const init = () => {
|
||||
height.value = document.documentElement.clientHeight - 94.5 + 'px'
|
||||
nextTick(() => {
|
||||
loading.value = true
|
||||
if (!frameRef.value) return
|
||||
frameRef.value.onload = () => {
|
||||
loading.value = false
|
||||
}
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
init()
|
||||
}, 300)
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
watch(
|
||||
() => props.src,
|
||||
() => {
|
||||
init()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<div v-loading="loading" :style="'height:' + height">
|
||||
<div
|
||||
v-loading="loading"
|
||||
class="w-full h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-2px)]"
|
||||
>
|
||||
<iframe
|
||||
ref="frameRef"
|
||||
:src="props.src"
|
||||
frameborder="no"
|
||||
frameborder="0"
|
||||
scrolling="auto"
|
||||
style="width: 100%; height: 100%"
|
||||
height="100%"
|
||||
width="100%"
|
||||
allowfullscreen="true"
|
||||
webkitallowfullscreen="true"
|
||||
mozallowfullscreen="true"
|
||||
></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -17,7 +17,7 @@ const title = computed(() => appStore.getTitle)
|
|||
<template>
|
||||
<div
|
||||
:class="prefixCls"
|
||||
class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)]"
|
||||
class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)] overflow-hidden"
|
||||
>
|
||||
<span class="text-14px">Copyright ©2022-{{ title }}</span>
|
||||
</div>
|
||||
|
|
|
@ -195,6 +195,16 @@ $prefix-cls: #{$namespace}-menu;
|
|||
}
|
||||
}
|
||||
|
||||
// 垂直菜单
|
||||
&__vertical {
|
||||
:deep(.#{$elNamespace}-menu--vertical) {
|
||||
&:not(.#{$elNamespace}-menu--collapse) .#{$elNamespace}-sub-menu__title,
|
||||
.#{$elNamespace}-menu-item {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 水平菜单
|
||||
&__horizontal {
|
||||
height: calc(var(--top-tool-height)) !important;
|
||||
|
|
|
@ -139,7 +139,7 @@ export default defineComponent({
|
|||
id={`${variables.namespace}-menu`}
|
||||
class={[
|
||||
prefixCls,
|
||||
'relative bg-[var(--left-menu-bg-color)] top-1px layout-border__right',
|
||||
'relative bg-[var(--left-menu-bg-color)] layout-border__right',
|
||||
{
|
||||
'w-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||
'w-[var(--tab-menu-min-width)]': unref(collapse)
|
||||
|
@ -147,7 +147,7 @@ export default defineComponent({
|
|||
]}
|
||||
onMouseleave={mouseleave}
|
||||
>
|
||||
<ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height)-1px)]">
|
||||
<ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height))]">
|
||||
<div>
|
||||
{() => {
|
||||
return unref(tabRouters).map((v) => {
|
||||
|
@ -199,7 +199,7 @@ export default defineComponent({
|
|||
{
|
||||
'!left-[var(--tab-menu-min-width)]': unref(collapse),
|
||||
'!left-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||
'!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),
|
||||
'!w-[var(--left-menu-max-width)]': unref(showMenu) || unref(fixedMenu),
|
||||
'!w-0': !unref(showMenu) && !unref(fixedMenu)
|
||||
}
|
||||
]}
|
||||
|
|
|
@ -280,7 +280,7 @@ watch(
|
|||
</span>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<ElScrollbar ref="scrollbarRef" class="h-full" @scroll="scroll">
|
||||
<div class="h-full flex">
|
||||
<div class="h-[var(--tags-view-height)] flex">
|
||||
<ContextMenu
|
||||
v-for="item in visitedViews"
|
||||
:key="item.fullPath"
|
||||
|
@ -491,10 +491,10 @@ $prefix-cls: #{$namespace}-tags-view;
|
|||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 1px);
|
||||
height: 100%;
|
||||
border-left: 1px solid var(--el-border-color);
|
||||
content: '';
|
||||
}
|
||||
|
@ -502,10 +502,10 @@ $prefix-cls: #{$namespace}-tags-view;
|
|||
&--first {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 1px);
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--el-border-color);
|
||||
border-left: none;
|
||||
content: '';
|
||||
|
@ -515,7 +515,7 @@ $prefix-cls: #{$namespace}-tags-view;
|
|||
|
||||
&__item {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
top: 3px;
|
||||
height: calc(100% - 6px);
|
||||
padding-right: 15px;
|
||||
margin-left: 4px;
|
||||
|
@ -523,6 +523,7 @@ $prefix-cls: #{$namespace}-tags-view;
|
|||
cursor: pointer;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--close {
|
||||
position: absolute;
|
||||
|
@ -562,10 +563,11 @@ $prefix-cls: #{$namespace}-tags-view;
|
|||
}
|
||||
|
||||
&__item--immerse {
|
||||
top: 3px;
|
||||
top: 2px;
|
||||
height: calc(100% - 3px);
|
||||
padding-right: 35px;
|
||||
margin: 0 -10px;
|
||||
border: 1px solid transparent;
|
||||
border: none !important;
|
||||
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='68' height='34' viewBox='0 0 68 34' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m27,0c-7.99582,0 -11.95105,0.00205 -12,12l0,6c0,8.284 -0.48549,16.49691 -8.76949,16.49691l54.37857,-0.11145c-8.284,0 -8.60908,-8.10146 -8.60908,-16.38546l0,-6c0.11145,-12.08445 -4.38441,-12 -12,-12l-13,0z' fill='%23409eff'/%3E%3C/svg%3E")
|
||||
12 27 15;
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ export const useRenderLayout = () => {
|
|||
|
||||
<ToolHeader class="flex-1"></ToolHeader>
|
||||
</div>
|
||||
<div class="absolute left-0 top-[var(--logo-height)+1px] h-[calc(100%-1px-var(--logo-height))] w-full flex">
|
||||
<div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-full flex">
|
||||
<Menu class="relative layout-border__right !h-full"></Menu>
|
||||
<div
|
||||
class={[
|
||||
|
@ -157,9 +157,9 @@ export const useRenderLayout = () => {
|
|||
'layout-border__bottom absolute',
|
||||
{
|
||||
'!fixed top-0 left-0 z-10': fixedHeader.value,
|
||||
'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)] mt-[calc(var(--logo-height)+1px)]':
|
||||
'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)] mt-[var(--logo-height)]':
|
||||
collapse.value && fixedHeader.value,
|
||||
'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)] mt-[calc(var(--logo-height)+1px)]':
|
||||
'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)] mt-[var(--logo-height)]':
|
||||
!collapse.value && fixedHeader.value
|
||||
}
|
||||
]}
|
||||
|
@ -190,24 +190,14 @@ export const useRenderLayout = () => {
|
|||
<Menu class="h-[var(--top-tool-height)] flex-1 px-10px"></Menu>
|
||||
<ToolHeader></ToolHeader>
|
||||
</div>
|
||||
<div
|
||||
class={[
|
||||
`${prefixCls}-content`,
|
||||
'w-full',
|
||||
{
|
||||
'h-[calc(100%-var(--app-footer-height))]': !fixedHeader.value,
|
||||
'h-[calc(100%-var(--tags-view-height)-var(--app-footer-height))]': fixedHeader.value
|
||||
}
|
||||
]}
|
||||
>
|
||||
<div class={[`${prefixCls}-content`, 'w-full h-[calc(100%-var(--top-tool-height))]']}>
|
||||
<ElScrollbar
|
||||
v-loading={pageLoading.value}
|
||||
class={[
|
||||
`${prefixCls}-content-scrollbar`,
|
||||
{
|
||||
'mt-[var(--tags-view-height)] !pb-[calc(var(--tags-view-height)+var(--app-footer-height))]':
|
||||
fixedHeader.value,
|
||||
'pb-[var(--app-footer-height)]': !fixedHeader.value
|
||||
'!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':
|
||||
fixedHeader.value
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
@ -216,7 +206,7 @@ export const useRenderLayout = () => {
|
|||
class={[
|
||||
'layout-border__bottom layout-border__top relative',
|
||||
{
|
||||
'!fixed w-full top-[calc(var(--top-tool-height)+1px)] left-0': fixedHeader.value
|
||||
'!fixed w-full top-[var(--top-tool-height)] left-0': fixedHeader.value
|
||||
}
|
||||
]}
|
||||
style="transition: width var(--transition-time-02), left var(--transition-time-02);"
|
||||
|
@ -238,7 +228,7 @@ export const useRenderLayout = () => {
|
|||
|
||||
<ToolHeader class="flex-1"></ToolHeader>
|
||||
</div>
|
||||
<div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-[calc(100%-2px)] flex">
|
||||
<div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-full flex">
|
||||
<TabMenu></TabMenu>
|
||||
<div
|
||||
class={[
|
||||
|
@ -270,18 +260,16 @@ export const useRenderLayout = () => {
|
|||
{tagsView.value ? (
|
||||
<TagsView
|
||||
class={[
|
||||
'relative layout-border__bottom layout-border__top',
|
||||
'relative layout-border__bottom',
|
||||
{
|
||||
'!fixed top-0 left-0 z-10': fixedHeader.value,
|
||||
'w-[calc(100%-var(--tab-menu-min-width))] !left-[var(--tab-menu-min-width)] mt-[var(--logo-height)]':
|
||||
collapse.value && fixedHeader.value,
|
||||
collapse.value && fixedHeader.value && !fixedMenu.value,
|
||||
'w-[calc(100%-var(--tab-menu-max-width))] !left-[var(--tab-menu-max-width)] mt-[var(--logo-height)]':
|
||||
!collapse.value && fixedHeader.value,
|
||||
'!fixed top-0 !left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] z-10':
|
||||
fixedHeader.value && fixedMenu.value,
|
||||
'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] !left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
|
||||
!collapse.value && fixedHeader.value && !fixedMenu.value,
|
||||
'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] !left-[calc(var(--tab-menu-min-width)+var(--left-menu-max-width))] mt-[var(--logo-height)]':
|
||||
collapse.value && fixedHeader.value && fixedMenu.value,
|
||||
'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] !left-[var(--tab-menu-max-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
|
||||
'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] !left-[calc(var(--tab-menu-max-width)+var(--left-menu-max-width))] mt-[var(--logo-height)]':
|
||||
!collapse.value && fixedHeader.value && fixedMenu.value
|
||||
}
|
||||
]}
|
||||
|
|
|
@ -56,20 +56,20 @@ export const defaultShortcuts = [
|
|||
* 时间日期转换
|
||||
* @param date 当前时间,new Date() 格式
|
||||
* @param format 需要转换的时间格式字符串
|
||||
* @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
|
||||
* @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
|
||||
* @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
|
||||
* @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
|
||||
* @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
|
||||
* @description format 字符串随意,如 `YYYY-MM、YYYY-MM-DD`
|
||||
* @description format 季度:"YYYY-MM-DD HH:mm:ss QQQQ"
|
||||
* @description format 星期:"YYYY-MM-DD HH:mm:ss WWW"
|
||||
* @description format 几周:"YYYY-MM-DD HH:mm:ss ZZZ"
|
||||
* @description format 季度 + 星期 + 几周:"YYYY-MM-DD HH:mm:ss WWW QQQQ ZZZ"
|
||||
* @returns 返回拼接后的时间字符串
|
||||
*/
|
||||
export function formatDate(date: Date, format?: string): string {
|
||||
// 日期不存在,则返回空
|
||||
if (!date) {
|
||||
return '';
|
||||
return ''
|
||||
}
|
||||
// 日期存在,则进行格式化
|
||||
return date ? dayjs(date).format(format ?? 'YYYY-MM-DD HH:mm:ss') : '';
|
||||
return date ? dayjs(date).format(format ?? 'YYYY-MM-DD HH:mm:ss') : ''
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,7 +110,7 @@ export function getWeek(dateTime: Date): number {
|
|||
* @description param 3天: 60 * 60* 24 * 1000 * 3
|
||||
* @returns 返回拼接后的时间字符串
|
||||
*/
|
||||
export function formatPast(param: string | Date, format = 'YYYY-mm-dd HH:MM:SS'): string {
|
||||
export function formatPast(param: string | Date, format = 'YYYY-MM-DD HH:mm:ss'): string {
|
||||
// 传入格式处理、存储转换值
|
||||
let t: any, s: number
|
||||
// 获取js 时间戳
|
||||
|
|
|
@ -0,0 +1,588 @@
|
|||
<template>
|
||||
<ContentWrap :bodyStyle="{ padding: '10px 20px' }" class="position-relative">
|
||||
<!-- TODO @GoldenZqqq:建议 svgs 里面新建一个 bpm;把相关的图标放进去哈 -->
|
||||
<Icon
|
||||
class="!position-fixed right-80px"
|
||||
:size="130"
|
||||
:icon="`svg-icon:audit${processInstance.status}`"
|
||||
/>
|
||||
<div class="text-#878c93">编号:{{ id }}</div>
|
||||
<el-divider class="!my-8px" />
|
||||
<div class="flex items-center gap-5 mb-10px">
|
||||
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="processInstance.status" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-5 mb-10px text-13px">
|
||||
<div class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600">
|
||||
<img class="rounded-full h-28px" src="@/assets/imgs/avatar.jpg" alt="" />
|
||||
{{ processInstance?.startUser?.nickname }}
|
||||
</div>
|
||||
<div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
|
||||
</div>
|
||||
|
||||
<el-tabs>
|
||||
<!-- 表单信息 -->
|
||||
<el-tab-pane label="表单信息">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="18" class="!flex !flex-col formCol">
|
||||
<!-- 表单信息 -->
|
||||
<div v-loading="processInstanceLoading" class="form-box flex flex-col mb-30px flex-1">
|
||||
<!-- 情况一:流程表单 -->
|
||||
<el-col
|
||||
v-if="processInstance?.processDefinition?.formType === 10"
|
||||
:offset="6"
|
||||
:span="16"
|
||||
>
|
||||
<form-create
|
||||
v-model="detailForm.value"
|
||||
v-model:api="fApi"
|
||||
:option="detailForm.option"
|
||||
:rule="detailForm.rule"
|
||||
/>
|
||||
</el-col>
|
||||
<!-- 情况二:业务表单 -->
|
||||
<div v-if="processInstance?.processDefinition?.formType === 20">
|
||||
<BusinessFormComponent :id="processInstance.businessKey" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO @GoldenZqqq:可以把下面 runningTask 拆一个小组件么 -->
|
||||
<el-affix target=".formCol" position="bottom" class="h-50px" v-if="runningTask?.id">
|
||||
<el-divider class="!mb-8px !mt-0" />
|
||||
<div
|
||||
class="pl-50px text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
|
||||
>
|
||||
<el-popover :visible="passVisible" placement="top-end" :width="500" trigger="click">
|
||||
<template #reference>
|
||||
<el-button plain type="success" @click="openPopover('1')">
|
||||
<Icon icon="ep:select" /> 通过
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="auditForm"
|
||||
:rules="auditRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item
|
||||
v-if="processInstance && processInstance.startUser"
|
||||
label="流程发起人"
|
||||
>
|
||||
{{ processInstance?.startUser.nickname }}
|
||||
<el-tag size="small" type="info" class="ml-8px">
|
||||
{{ processInstance?.startUser.deptName }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-card v-if="runningTask.formId > 0" class="mb-15px !-mt-10px">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">
|
||||
填写表单【{{ runningTask?.formName }}】
|
||||
</span>
|
||||
</template>
|
||||
<form-create
|
||||
v-model="approveForm.value"
|
||||
v-model:api="approveFormFApi"
|
||||
:option="approveForm.option"
|
||||
:rule="approveForm.rule"
|
||||
/>
|
||||
</el-card>
|
||||
<el-form-item label="审批建议" prop="reason">
|
||||
<el-input
|
||||
v-model="auditForm.reason"
|
||||
placeholder="请输入审批建议"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送人" prop="copyUserIds">
|
||||
<el-select
|
||||
v-model="auditForm.copyUserIds"
|
||||
multiple
|
||||
placeholder="请选择抄送人"
|
||||
>
|
||||
<el-option
|
||||
v-for="itemx in userOptions"
|
||||
:key="itemx.id"
|
||||
:label="itemx.nickname"
|
||||
:value="itemx.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
:disabled="formLoading"
|
||||
type="success"
|
||||
@click="handleAudit(true)"
|
||||
>
|
||||
通过
|
||||
</el-button>
|
||||
<el-button @click="passVisible = false"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-popover>
|
||||
<el-popover
|
||||
:visible="rejectVisible"
|
||||
placement="top-end"
|
||||
:width="500"
|
||||
trigger="click"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button class="mr-20px" plain type="danger" @click="openPopover('2')">
|
||||
<Icon icon="ep:close" /> 拒绝
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
|
||||
<el-form
|
||||
label-position="top"
|
||||
class="mb-auto"
|
||||
ref="formRef"
|
||||
:model="auditForm"
|
||||
:rules="auditRule"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item
|
||||
v-if="processInstance && processInstance.startUser"
|
||||
label="流程发起人"
|
||||
>
|
||||
{{ processInstance?.startUser.nickname }}
|
||||
<el-tag size="small" type="info" class="ml-8px">
|
||||
{{ processInstance?.startUser.deptName }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-card v-if="runningTask.formId > 0" class="mb-15px !-mt-10px">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">
|
||||
填写表单【{{ runningTask?.formName }}】
|
||||
</span>
|
||||
</template>
|
||||
<form-create
|
||||
v-model="approveForm.value"
|
||||
v-model:api="approveFormFApi"
|
||||
:option="approveForm.option"
|
||||
:rule="approveForm.rule"
|
||||
/>
|
||||
</el-card>
|
||||
<el-form-item label="审批建议" prop="reason">
|
||||
<el-input
|
||||
v-model="auditForm.reason"
|
||||
placeholder="请输入审批建议"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送人" prop="copyUserIds">
|
||||
<el-select
|
||||
v-model="auditForm.copyUserIds"
|
||||
multiple
|
||||
placeholder="请选择抄送人"
|
||||
>
|
||||
<el-option
|
||||
v-for="itemx in userOptions"
|
||||
:key="itemx.id"
|
||||
:label="itemx.nickname"
|
||||
:value="itemx.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
:disabled="formLoading"
|
||||
type="danger"
|
||||
@click="handleAudit(false)"
|
||||
>
|
||||
拒绝
|
||||
</el-button>
|
||||
<el-button @click="rejectVisible = false"> 取消 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div @click="handleSend"> <Icon :size="14" icon="svg-icon:send" /> 抄送 </div>
|
||||
<div @click="openTaskUpdateAssigneeForm">
|
||||
<Icon :size="14" icon="fa:share-square-o" /> 转交
|
||||
</div>
|
||||
<div @click="handleDelegate">
|
||||
<Icon :size="14" icon="ep:position" /> 委派
|
||||
</div>
|
||||
<div @click="handleSign"> <Icon :size="14" icon="ep:plus" /> 加签 </div>
|
||||
<div @click="handleBack"> <Icon :size="14" icon="fa:mail-reply" /> 退回 </div>
|
||||
</div>
|
||||
</el-affix>
|
||||
|
||||
<!-- TODO @GoldenZqqq:后续这个,也拆个小组件出来 -->
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-timeline class="pt-20px">
|
||||
<el-timeline-item type="primary" size="large">
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<div class="font-bold"> 发起人:{{ processInstance?.startUser?.nickname }}</div>
|
||||
<el-tag type="success">发起</el-tag>
|
||||
<div class="text-#a5a5a5 text-12px">
|
||||
发起时间:{{ formatDate(processInstance.startTime) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item
|
||||
v-for="(activity, index) in tasks"
|
||||
:key="index"
|
||||
type="primary"
|
||||
size="large"
|
||||
>
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<div class="font-bold"> 审批人:{{ activity.assigneeUser?.nickname }}</div>
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
|
||||
:value="activity.status"
|
||||
/>
|
||||
<!-- TODO:暂无该字段 -->
|
||||
<div v-if="activity.receiveTime" class="text-#a5a5a5 text-12px">
|
||||
接收时间:{{ formatDate(activity.receiveTime) }}
|
||||
</div>
|
||||
<div v-if="activity.createTime" class="text-#a5a5a5 text-12px">
|
||||
审批时间:{{ formatDate(activity.createTime) }}
|
||||
</div>
|
||||
<div v-if="activity.opinion" class="text-#a5a5a5 text-12px w-100%">
|
||||
<div class="mb-5px">审批意见:</div>
|
||||
<div
|
||||
class="w-100% border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
|
||||
>
|
||||
{{ activity.opinion }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 该节点用户的头像 -->
|
||||
<!-- <template #dot>
|
||||
<img :src="activity?.avatar" alt="" />
|
||||
</template> -->
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
<!-- 流程图 -->
|
||||
<el-tab-pane label="流程图">
|
||||
<!-- 高亮流程图 -->
|
||||
<ProcessInstanceBpmnViewer
|
||||
:id="`${id}`"
|
||||
:bpmn-xml="bpmnXml"
|
||||
:loading="processInstanceLoading"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<!-- 流转记录 -->
|
||||
<el-tab-pane label="流转记录">
|
||||
<!-- 审批记录 -->
|
||||
<ProcessInstanceTaskList
|
||||
:loading="tasksLoad"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
@refresh="getTaskList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<!-- 流转评论 -->
|
||||
<el-tab-pane label="流转评论"> 流转评论 </el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 弹窗:转派审批人 -->
|
||||
<TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
|
||||
<!-- 弹窗:回退节点 -->
|
||||
<TaskReturnForm ref="taskReturnFormRef" @success="getDetail" />
|
||||
<!-- 弹窗:委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
|
||||
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
|
||||
<!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
|
||||
<TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
||||
import TaskReturnForm from './dialog/TaskReturnForm.vue'
|
||||
import TaskDelegateForm from './dialog/TaskDelegateForm.vue'
|
||||
import TaskTransferForm from './dialog/TaskTransferForm.vue'
|
||||
import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
|
||||
import { registerComponent } from '@/utils/routerHelper'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
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 string // 流程实例的编号
|
||||
const processInstanceLoading = ref(false) // 流程实例的加载中
|
||||
const formLoading = ref(false) // 表单加载中
|
||||
const passVisible = ref(false) // 是否显示
|
||||
const rejectVisible = ref(false) // 是否显示
|
||||
const processInstance = ref<any>({}) // 流程实例
|
||||
const bpmnXml = ref('') // BPMN XML
|
||||
const tasksLoad = ref(true) // 任务的加载中
|
||||
const tasks = ref<any[]>([]) // 任务列表
|
||||
// ========== 审批信息 ==========
|
||||
const runningTask = ref<any>({}) // 运行中的任务
|
||||
const formRef = ref()
|
||||
const auditForm = ref<any>({}) // 审批任务的表单
|
||||
const auditRule = reactive({
|
||||
reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const approveForm = ref<any>({}) // 审批通过时,额外的补充信息
|
||||
const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
|
||||
|
||||
// ========== 申请信息 ==========
|
||||
const fApi = ref<ApiAttrs>() //
|
||||
const detailForm = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程实例的表单详情
|
||||
|
||||
/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
|
||||
watch(
|
||||
() => approveFormFApi.value,
|
||||
(val) => {
|
||||
val?.btn?.show(false)
|
||||
val?.resetBtn?.show(false)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
/** 处理审批通过和不通过的操作 */
|
||||
const handleAudit = async (pass) => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const auditFormRef = proxy.$refs['formRef']
|
||||
// 1.2 校验表单
|
||||
const elForm = unref(auditFormRef)
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 2.1 提交审批
|
||||
const data = {
|
||||
id: runningTask.value.id,
|
||||
reason: auditForm.value.reason,
|
||||
copyUserIds: auditForm.value.copyUserIds
|
||||
}
|
||||
if (pass) {
|
||||
// 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||
const formCreateApi = approveFormFApi.value
|
||||
if (Object.keys(formCreateApi)?.length > 0) {
|
||||
await formCreateApi.validate()
|
||||
// @ts-ignore
|
||||
data.variables = approveForm.value.value
|
||||
}
|
||||
await TaskApi.approveTask(data)
|
||||
message.success('审批通过成功')
|
||||
} else {
|
||||
await TaskApi.rejectTask(data)
|
||||
message.success('审批不通过成功')
|
||||
}
|
||||
// 2.2 加载最新数据
|
||||
getDetail()
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 转派审批人 */
|
||||
const taskTransferFormRef = ref()
|
||||
const openTaskUpdateAssigneeForm = () => {
|
||||
taskTransferFormRef.value.open(runningTask.value.id)
|
||||
}
|
||||
|
||||
/** 处理审批退回的操作 */
|
||||
const taskDelegateForm = ref()
|
||||
const handleDelegate = async () => {
|
||||
taskDelegateForm.value.open(runningTask.value.id)
|
||||
}
|
||||
|
||||
/** 处理审批退回的操作 */
|
||||
const taskReturnFormRef = ref()
|
||||
const handleBack = async () => {
|
||||
taskReturnFormRef.value.open(runningTask.value.id)
|
||||
}
|
||||
|
||||
/** 处理审批加签的操作 */
|
||||
const taskSignCreateFormRef = ref()
|
||||
const handleSign = async () => {
|
||||
taskSignCreateFormRef.value.open(runningTask.value.id)
|
||||
}
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = () => {
|
||||
// 1. 获得流程实例相关
|
||||
getProcessInstance()
|
||||
// 2. 获得流程任务列表(审批记录)
|
||||
getTaskList()
|
||||
}
|
||||
|
||||
/** 加载流程实例 */
|
||||
const BusinessFormComponent = ref<any>(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) {
|
||||
setConfAndFields2(
|
||||
detailForm,
|
||||
processDefinition.formConf,
|
||||
processDefinition.formFields,
|
||||
data.formVariables
|
||||
)
|
||||
nextTick().then(() => {
|
||||
fApi.value?.btn.show(false)
|
||||
fApi.value?.resetBtn.show(false)
|
||||
fApi.value?.disabled(true)
|
||||
})
|
||||
} else {
|
||||
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
||||
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
|
||||
}
|
||||
|
||||
// 加载流程图
|
||||
bpmnXml.value = (await DefinitionApi.getProcessDefinition(processDefinition.id))?.bpmnXml
|
||||
} finally {
|
||||
processInstanceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载任务列表 */
|
||||
const getTaskList = async () => {
|
||||
runningTask.value = {}
|
||||
auditForm.value = {}
|
||||
approveForm.value = {}
|
||||
approveFormFApi.value = {}
|
||||
try {
|
||||
// 获得未取消的任务
|
||||
tasksLoad.value = true
|
||||
const data = await TaskApi.getTaskListByProcessInstanceId(id)
|
||||
tasks.value = []
|
||||
// 1.1 移除已取消的审批
|
||||
data.forEach((task) => {
|
||||
if (task.status !== 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
|
||||
}
|
||||
})
|
||||
|
||||
// 获得需要自己审批的任务
|
||||
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.status !== 1 && task.status !== 6) {
|
||||
return
|
||||
}
|
||||
// 2.2 自己不是处理人
|
||||
if (!task.assigneeUser || task.assigneeUser.id !== userId) {
|
||||
return
|
||||
}
|
||||
// 2.3 添加到处理任务
|
||||
runningTask.value = { ...task }
|
||||
auditForm.value = {
|
||||
reason: '',
|
||||
copyUserIds: []
|
||||
}
|
||||
|
||||
// 2.4 处理 approve 表单
|
||||
if (task.formId && task.formConf) {
|
||||
const tempApproveForm = {}
|
||||
setConfAndFields2(tempApproveForm, task.formConf, task.formFields, task.formVariables)
|
||||
approveForm.value = tempApproveForm
|
||||
} else {
|
||||
approveForm.value = {} // 占位,避免为空
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* 抄送 TODO */
|
||||
const handleSend = () => {}
|
||||
|
||||
const openPopover = (flag) => {
|
||||
passVisible.value = false
|
||||
rejectVisible.value = false
|
||||
formRef.value.resetFields()
|
||||
flag === '1' ? (passVisible.value = true) : (rejectVisible.value = true)
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
getDetail()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-box {
|
||||
:deep(.el-card) {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
:deep(.el-affix--fixed) {
|
||||
background-color: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
> div {
|
||||
margin: 0 15px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:hover {
|
||||
color: #6db5ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -2,8 +2,8 @@
|
|||
<doc-alert title="数据库 MyBatis" url="https://doc.iocoder.cn/mybatis/" />
|
||||
<doc-alert title="多数据源(读写分离)" url="https://doc.iocoder.cn/dynamic-datasource/" />
|
||||
|
||||
<ContentWrap>
|
||||
<IFrame v-if="!loading" :src="url" />
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="url" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
|
||||
|
||||
<ContentWrap>
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
|
||||
|
||||
<ContentWrap>
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<doc-alert title="接口文档" url="https://doc.iocoder.cn/api-doc/" />
|
||||
|
||||
<ContentWrap>
|
||||
<IFrame :src="src" />
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -29,6 +29,17 @@
|
|||
</el-table-column>
|
||||
<el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
|
||||
<el-table-column align="center" label="库存" min-width="90" prop="stock" />
|
||||
<el-table-column v-if="spuData.length > 1 && isDelete" align="center" label="操作" min-width="90" >
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="deleteSpu(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<script generic="T extends Spu" lang="ts" setup>
|
||||
|
@ -40,10 +51,13 @@ import { SpuProperty } from '@/views/mall/promotion/components/index'
|
|||
|
||||
defineOptions({ name: 'PromotionSpuAndSkuList' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps<{
|
||||
spuList: T[]
|
||||
ruleConfig: RuleConfig[]
|
||||
spuPropertyListP: SpuProperty<T>[]
|
||||
isDelete?: boolean //spu是否可以多选
|
||||
}>()
|
||||
|
||||
const spuData = ref<Spu[]>([]) // spu 详情数据列表
|
||||
|
@ -77,6 +91,19 @@ const imagePreview = (imgUrl: string) => {
|
|||
})
|
||||
}
|
||||
|
||||
// 删除时的触发事件
|
||||
const emits = defineEmits<{
|
||||
(e: 'delete', spuId: number): void
|
||||
}>()
|
||||
|
||||
/** 多选时可以删除spu **/
|
||||
const deleteSpu = async (spuId: number) => {
|
||||
await message.confirm('是否删除商品编号为' + spuId + '的数据?')
|
||||
let index = spuData.value.findIndex((item) => item.id == spuId)
|
||||
spuData.value.splice(index,1);
|
||||
emits('delete',spuId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将传进来的值赋值给 skuList
|
||||
*/
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
:rule-config="ruleConfig"
|
||||
:spu-list="spuList"
|
||||
:spu-property-list-p="spuPropertyList"
|
||||
:isDelete="true"
|
||||
@delete="deleteSpu"
|
||||
>
|
||||
<el-table-column align="center" label="优惠金额" min-width="168">
|
||||
<template #default="{ row: sku }">
|
||||
|
@ -47,6 +49,7 @@ import { cloneDeep } from 'lodash-es'
|
|||
import * as DiscountActivityApi from '@/api/mall/promotion/discount/discountActivity'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
|
||||
import {formatToFraction} from "@/utils";
|
||||
|
||||
defineOptions({ name: 'PromotionDiscountActivityForm' })
|
||||
|
||||
|
@ -65,8 +68,8 @@ const spuAndSkuListRef = ref() // sku 限时折扣 配置组件Ref
|
|||
const ruleConfig: RuleConfig[] = []
|
||||
const spuList = ref<DiscountActivityApi.SpuExtension[]>([]) // 选择的 spu
|
||||
const spuPropertyList = ref<SpuProperty<DiscountActivityApi.SpuExtension>[]>([])
|
||||
const spuIds = ref<number[]>([]);
|
||||
const selectSpu = (spuId: number, skuIds: number[]) => {
|
||||
formRef.value.setValues({ spuId })
|
||||
getSpuDetails(spuId, skuIds)
|
||||
}
|
||||
/**
|
||||
|
@ -75,14 +78,22 @@ const selectSpu = (spuId: number, skuIds: number[]) => {
|
|||
const getSpuDetails = async (
|
||||
spuId: number,
|
||||
skuIds: number[] | undefined,
|
||||
products?: DiscountActivityApi.DiscountProductVO[]
|
||||
products?: DiscountActivityApi.DiscountProductVO[],
|
||||
type?: string
|
||||
) => {
|
||||
const spuProperties: SpuProperty<DiscountActivityApi.SpuExtension>[] = []
|
||||
//如果已经包含spu则跳过
|
||||
if(spuIds.value.includes(spuId)){
|
||||
if(type !== "load"){
|
||||
message.error("数据重复选择!")
|
||||
}
|
||||
return;
|
||||
}
|
||||
spuIds.value.push(spuId)
|
||||
const res = (await ProductSpuApi.getSpuDetailList([spuId])) as DiscountActivityApi.SpuExtension[]
|
||||
if (res.length == 0) {
|
||||
return
|
||||
}
|
||||
spuList.value = []
|
||||
//spuList.value = []
|
||||
// 因为只能选择一个
|
||||
const spu = res[0]
|
||||
const selectSkus =
|
||||
|
@ -100,15 +111,19 @@ const getSpuDetails = async (
|
|||
config = product || config
|
||||
}
|
||||
sku.productConfig = config
|
||||
sku.price = formatToFraction(sku.price)
|
||||
sku.marketPrice = formatToFraction(sku.marketPrice)
|
||||
sku.costPrice = formatToFraction(sku.costPrice)
|
||||
sku.firstBrokeragePrice = formatToFraction(sku.firstBrokeragePrice)
|
||||
sku.secondBrokeragePrice = formatToFraction(sku.secondBrokeragePrice)
|
||||
})
|
||||
spu.skus = selectSkus as DiscountActivityApi.SkuExtension[]
|
||||
spuProperties.push({
|
||||
spuPropertyList.value.push({
|
||||
spuId: spu.id!,
|
||||
spuDetail: spu,
|
||||
propertyList: getPropertyList(spu)
|
||||
})
|
||||
spuList.value.push(spu)
|
||||
spuPropertyList.value = spuProperties
|
||||
}
|
||||
|
||||
// ================= end =================
|
||||
|
@ -126,8 +141,10 @@ const open = async (type: string, id?: number) => {
|
|||
const data = (await DiscountActivityApi.getDiscountActivity(
|
||||
id
|
||||
)) as DiscountActivityApi.DiscountActivityVO
|
||||
const supId = data.products[0].spuId
|
||||
await getSpuDetails(supId!, data.products?.map((sku) => sku.skuId), data.products)
|
||||
for (let productsKey in data.products) {
|
||||
const supId = data.products[productsKey].spuId
|
||||
await getSpuDetails(supId!, data.products?.map((sku) => sku.skuId), data.products,"load")
|
||||
}
|
||||
formRef.value.setValues(data)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
|
@ -149,9 +166,20 @@ const submitForm = async () => {
|
|||
const data = formRef.value.formModel as DiscountActivityApi.DiscountActivityVO
|
||||
// 获取 折扣商品配置
|
||||
const products = cloneDeep(spuAndSkuListRef.value.getSkuConfigs('productConfig'))
|
||||
let timp = false;
|
||||
products.forEach((item: DiscountActivityApi.DiscountProductVO) => {
|
||||
item.discountType = data['discountType']
|
||||
if(item.discountPrice != null && item.discountPrice > 0){
|
||||
item.discountType = 1
|
||||
}else if(item.discountPercent != null && item.discountPercent > 0){
|
||||
item.discountType = 2
|
||||
}else{
|
||||
timp = true
|
||||
}
|
||||
})
|
||||
if(timp){
|
||||
message.error("优惠金额和折扣百分比需要填写一个");
|
||||
return;
|
||||
}
|
||||
data.products = products
|
||||
// 真正提交
|
||||
if (formType.value === 'create') {
|
||||
|
@ -173,7 +201,16 @@ const submitForm = async () => {
|
|||
const resetForm = async () => {
|
||||
spuList.value = []
|
||||
spuPropertyList.value = []
|
||||
spuIds.value = []
|
||||
await nextTick()
|
||||
formRef.value.getElFormRef().resetFields()
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除spu
|
||||
*/
|
||||
const deleteSpu = (spuId: number) => {
|
||||
spuIds.value.splice(spuIds.value.findIndex((item) => item == spuId), 1)
|
||||
spuPropertyList.value.splice(spuPropertyList.value.findIndex((item) => item.spuId == spuId), 1)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -72,17 +72,6 @@ const crudSchemas = reactive<CrudSchema[]>([
|
|||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '优惠类型',
|
||||
field: 'discountType',
|
||||
dictType: DICT_TYPE.PROMOTION_DISCOUNT_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
form: {
|
||||
component: 'Radio',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '活动商品',
|
||||
field: 'spuId',
|
||||
|
|
|
@ -70,17 +70,17 @@
|
|||
~ {{ formatDate(scope.row.endTime, 'YYYY-MM-DD') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品图片" prop="spuName" min-width="80">
|
||||
<template #default="scope">
|
||||
<el-image
|
||||
:src="scope.row.picUrl"
|
||||
class="h-40px w-40px"
|
||||
:preview-src-list="[scope.row.picUrl]"
|
||||
preview-teleported
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品标题" prop="spuName" min-width="300" />
|
||||
<!-- <el-table-column label="商品图片" prop="spuName" min-width="80">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <el-image-->
|
||||
<!-- :src="scope.row.picUrl"-->
|
||||
<!-- class="h-40px w-40px"-->
|
||||
<!-- :preview-src-list="[scope.row.picUrl]"-->
|
||||
<!-- preview-teleported-->
|
||||
<!-- />-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<!-- <el-table-column label="商品标题" prop="spuName" min-width="300" />-->
|
||||
<el-table-column label="活动状态" align="center" prop="status" min-width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
<MessageItem :message="item">
|
||||
<ProductItem
|
||||
v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
|
||||
:spuId="getMessageContent(item).spuId"
|
||||
:picUrl="getMessageContent(item).picUrl"
|
||||
:price="getMessageContent(item).price"
|
||||
:skuText="getMessageContent(item).introduction"
|
||||
|
@ -393,7 +394,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
|||
border-left: 5px solid transparent;
|
||||
border-bottom: 5px solid transparent;
|
||||
border-top: 5px solid transparent;
|
||||
border-right: 5px solid #ffffff;
|
||||
border-right: 5px solid var(--app-content-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -412,7 +413,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
|||
right: -19px;
|
||||
top: calc(50% - 10px);
|
||||
position: absolute;
|
||||
border-left: 5px solid #ffffff;
|
||||
border-left: 5px solid var(--app-content-bg-color);
|
||||
border-bottom: 5px solid transparent;
|
||||
border-top: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
|
@ -422,9 +423,9 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
|||
|
||||
// 消息气泡
|
||||
.kefu-message {
|
||||
color: #333;
|
||||
color: #A9A9A9;
|
||||
border-radius: 5px;
|
||||
box-shadow: 3px 5px 15px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 3px 3px 5px rgba(220,220,220, 0.1);
|
||||
padding: 5px 10px;
|
||||
width: auto;
|
||||
max-width: 50%;
|
||||
|
@ -432,7 +433,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
|||
display: inline-block !important;
|
||||
position: relative;
|
||||
word-break: break-all;
|
||||
background-color: #ffffff;
|
||||
background-color: var(--app-content-bg-color);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
|
@ -454,7 +455,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
|||
|
||||
.chat-tools {
|
||||
width: 100%;
|
||||
border: #e4e0e0 solid 1px;
|
||||
border: var(--el-border-color) solid 1px;
|
||||
border-radius: 10px;
|
||||
height: 44px;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<ProductItem
|
||||
v-for="item in list"
|
||||
:spu-id="item.spuId"
|
||||
:key="item.id"
|
||||
:picUrl="item.picUrl"
|
||||
:price="item.price"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-if="isObject(getMessageContent)">
|
||||
<div v-if="isObject(getMessageContent)" @click="openDetail(getMessageContent.id)" style="cursor: pointer;">
|
||||
<div :key="getMessageContent.id" class="order-list-card-box mt-14px">
|
||||
<div class="order-card-header flex items-center justify-between p-x-5px">
|
||||
<div class="order-no">订单号:{{ getMessageContent.no }}</div>
|
||||
|
@ -9,6 +9,7 @@
|
|||
</div>
|
||||
<div v-for="item in getMessageContent.items" :key="item.id" class="border-bottom">
|
||||
<ProductItem
|
||||
:spu-id="item.spuId"
|
||||
:num="item.count"
|
||||
:picUrl="item.picUrl"
|
||||
:price="item.price"
|
||||
|
@ -36,6 +37,8 @@ import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
|||
import { isObject } from '@/utils/is'
|
||||
import ProductItem from '@/views/mall/promotion/kefu/components/message/ProductItem.vue'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
defineOptions({ name: 'OrderItem' })
|
||||
const props = defineProps<{
|
||||
message?: KeFuMessageRespVO
|
||||
|
@ -46,6 +49,12 @@ const getMessageContent = computed(() =>
|
|||
typeof props.message !== 'undefined' ? jsonParse(props!.message!.content) : props.order
|
||||
)
|
||||
|
||||
/** 查看订单详情 */
|
||||
const openDetail = (id: number) => {
|
||||
console.log(getMessageContent)
|
||||
push({ name: 'TradeOrderDetail', params: { id } })
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化订单状态的颜色
|
||||
*
|
||||
|
@ -97,7 +106,7 @@ function formatOrderStatus(order: any) {
|
|||
.order-list-card-box {
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
border: 1px #6a6a6a solid;
|
||||
border: 1px var(--el-border-color) solid;
|
||||
background-color: var(--app-content-bg-color);
|
||||
|
||||
.order-card-header {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div @click.stop="openDetail(props.spuId)" style="cursor: pointer;">
|
||||
<div>
|
||||
<slot name="top"></slot>
|
||||
</div>
|
||||
|
@ -15,6 +15,7 @@
|
|||
class="order-img"
|
||||
fit="contain"
|
||||
preview-teleported
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
@ -53,8 +54,14 @@
|
|||
<script lang="ts" setup>
|
||||
import { fenToYuan } from '@/utils'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
defineOptions({ name: 'ProductItem' })
|
||||
const props = defineProps({
|
||||
spuId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
picUrl: {
|
||||
type: String,
|
||||
default: 'https://img1.baidu.com/it/u=1601695551,235775011&fm=26&fmt=auto'
|
||||
|
@ -107,13 +114,19 @@ const skuString = computed(() => {
|
|||
}
|
||||
return props.skuText
|
||||
})
|
||||
|
||||
/** 查看商品详情 */
|
||||
const openDetail = (spuId: number) => {
|
||||
console.log(props.spuId)
|
||||
push({ name: 'ProductSpuDetail', params: { id: spuId } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ss-order-card-warp {
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px #6a6a6a solid;
|
||||
border: 1px var(--el-border-color) solid;
|
||||
background-color: var(--app-content-bg-color);
|
||||
|
||||
.img-box {
|
||||
|
@ -134,7 +147,7 @@ const skuString = computed(() => {
|
|||
|
||||
.tool-box {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
right: 0;
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -325,13 +325,13 @@ onMounted(async () => {
|
|||
align-items: center;
|
||||
min-height: 30px;
|
||||
padding: 10px;
|
||||
background-color: #f7f8fa;
|
||||
background-color: var(--app-content-bg-color);
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 13px;
|
||||
border-color: transparent #f7f8fa transparent transparent; /* 尖角颜色,左侧朝向 */
|
||||
border-color: transparent var(--app-content-bg-color) transparent transparent; /* 尖角颜色,左侧朝向 */
|
||||
border-style: solid;
|
||||
border-width: 8px; /* 调整尖角大小 */
|
||||
content: '';
|
||||
|
|
|
@ -106,14 +106,8 @@
|
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
<el-dialog
|
||||
v-model="mapDialogVisible"
|
||||
title="获取经纬度"
|
||||
append-to-body
|
||||
width="500px"
|
||||
class="mapBox"
|
||||
>
|
||||
<iframe id="mapPage" width="100%" height="100%" frameborder="0" :src="tencentLbsUrl"></iframe>
|
||||
<el-dialog v-model="mapDialogVisible" title="获取经纬度" append-to-body>
|
||||
<IFrame class="h-609px" :src="tencentLbsUrl" />
|
||||
</el-dialog>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
@ -266,8 +260,3 @@ onMounted(async () => {
|
|||
await initTencentLbsMap()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mapBox .el-dialog__body {
|
||||
height: 640px !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
<ContentWrap>
|
||||
<doc-alert title="大屏设计器" url="https://doc.iocoder.cn/report/screen/" />
|
||||
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame :src="src" />
|
||||
</ContentWrap>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'GoView' })
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
<ContentWrap>
|
||||
<doc-alert title="报表设计器" url="https://doc.iocoder.cn/report/" />
|
||||
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame :src="src" />
|
||||
</ContentWrap>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
|
||||
defineOptions({ name: 'JimuReport' })
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const src = ref(BASE_URL + '/jmreport/list?token=' + getAccessToken())
|
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/jmreport/list?token=' + getAccessToken())
|
||||
</script>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<el-tag>{{ formData.code }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单权限">
|
||||
<el-card class="cardHeight">
|
||||
<el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
|
||||
<template #header>
|
||||
全选/全不选:
|
||||
<el-switch
|
||||
|
@ -151,10 +151,3 @@ const handleCheckedTreeExpand = () => {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.cardHeight {
|
||||
width: 100%;
|
||||
max-height: 400px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" title="菜单权限" width="800">
|
||||
<Dialog v-model="dialogVisible" title="数据权限" width="800">
|
||||
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
|
||||
<el-form-item label="角色名称">
|
||||
<el-tag>{{ formData.name }}</el-tag>
|
||||
|
@ -21,9 +21,9 @@
|
|||
<el-form-item
|
||||
v-if="formData.dataScope === SystemDataScopeEnum.DEPT_CUSTOM"
|
||||
label="权限范围"
|
||||
style="display: flex"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-card class="card" shadow="never">
|
||||
<el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
|
||||
<template #header>
|
||||
全选/全不选:
|
||||
<el-switch
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<el-input v-model="formData.name" placeholder="请输入套餐名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单权限">
|
||||
<el-card class="cardHeight">
|
||||
<el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
|
||||
<template #header>
|
||||
全选/全不选:
|
||||
<el-switch
|
||||
|
@ -185,10 +185,3 @@ const handleCheckedTreeExpand = () => {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.cardHeight {
|
||||
width: 100%;
|
||||
max-height: 400px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,7 +12,7 @@ export default defineConfig({
|
|||
${selector} {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 1px 10px 0;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
transition: background var(--transition-time-02);
|
||||
|
|
Loading…
Reference in New Issue