【代码优化】分支合并

This commit is contained in:
puhui999 2024-08-19 16:38:15 +08:00
commit ffdd3325dd
48 changed files with 1316 additions and 462 deletions

View File

@ -83,7 +83,8 @@
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit" "source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
}, },
"[vue]": { "[vue]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint" "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"

View File

@ -47,7 +47,7 @@
"driver.js": "^1.3.1", "driver.js": "^1.3.1",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0", "echarts-wordcloud": "^2.1.0",
"element-plus": "2.7.0", "element-plus": "2.8.0",
"fast-xml-parser": "^4.3.2", "fast-xml-parser": "^4.3.2",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",

View File

@ -1,7 +1,20 @@
import { getAccessToken } from '@/utils/auth' import { getAccessToken } from '@/utils/auth'
import { fetchEventSource } from '@microsoft/fetch-event-source' import { fetchEventSource } from '@microsoft/fetch-event-source'
import { config } from '@/config/axios/config' import { config } from '@/config/axios/config'
import request from '@/config/axios' // AI 思维导图 VO
// AI 思维导图 VO
export interface MindMapVO {
id: number // 编号
userId: number // 用户编号
prompt: string // 生成内容提示
generatedContent: string // 生成的思维导图内容
platform: string // 平台
model: string // 模型
errorMessage: string // 错误信息
}
// AI 思维导图生成 VO
export interface AiMindMapGenerateReqVO { export interface AiMindMapGenerateReqVO {
prompt: string prompt: string
} }
@ -34,5 +47,14 @@ export const AiMindMapApi = {
onclose: onClose, onclose: onClose,
signal: ctrl.signal signal: ctrl.signal
}) })
},
// 查询思维导图分页
getMindMapPage: async (params: any) => {
return await request.get({ url: `/ai/mind-map/page`, params })
},
// 删除思维导图
deleteMindMap: async (id: number) => {
return await request.delete({ url: `/ai/mind-map/delete?id=` + id })
} }
} }

View File

@ -24,20 +24,6 @@ export interface PropertyValueVO {
remark?: string remark?: string
} }
/**
*
*/
export interface PropertyValueDetailVO {
/** 属性项的编号 */
propertyId: number // 属性的编号
/** 属性的名称 */
propertyName: string
/** 属性值的编号 */
valueId: number
/** 属性值的名称 */
valueName: string
}
// ------------------------ 属性项 ------------------- // ------------------------ 属性项 -------------------
// 创建属性项 // 创建属性项
@ -65,6 +51,11 @@ export const getPropertyPage = (params: PageParam) => {
return request.get({ url: '/product/property/page', params }) return request.get({ url: '/product/property/page', params })
} }
// 获得属性项精简列表
export const getPropertySimpleList = (): Promise<PropertyVO[]> => {
return request.get({ url: '/product/property/simple-list' })
}
// ------------------------ 属性值 ------------------- // ------------------------ 属性值 -------------------
// 获得属性值分页 // 获得属性值分页
@ -91,3 +82,8 @@ export const updatePropertyValue = (data: PropertyValueVO) => {
export const deletePropertyValue = (id: number) => { export const deletePropertyValue = (id: number) => {
return request.delete({ url: `/product/property/value/delete?id=${id}` }) return request.delete({ url: `/product/property/value/delete?id=${id}` })
} }
// 获得属性值精简列表
export const getPropertyValueSimpleList = (propertyId: number): Promise<PropertyValueVO[]> => {
return request.get({ url: '/product/property/value/simple-list', params: { propertyId } })
}

View File

@ -50,6 +50,8 @@ export interface Spu {
giveIntegral?: number // 赠送积分 giveIntegral?: number // 赠送积分
virtualSalesCount?: number // 虚拟销量 virtualSalesCount?: number // 虚拟销量
price?: number // 商品价格 price?: number // 商品价格
combinationPrice?: number // 商品拼团价格
seckillPrice?: number // 商品秒杀价格
salesCount?: number // 商品销量 salesCount?: number // 商品销量
marketPrice?: number // 市场价 marketPrice?: number // 市场价
costPrice?: number // 成本价 costPrice?: number // 成本价

View File

@ -24,6 +24,7 @@ export interface SeckillActivityVO {
// 秒杀活动所需属性 // 秒杀活动所需属性
export interface SeckillProductVO { export interface SeckillProductVO {
skuId: number skuId: number
spuId: number
seckillPrice: number seckillPrice: number
stock: number stock: number
} }

View File

@ -2,6 +2,7 @@ import request from '@/config/axios'
export interface AppVO { export interface AppVO {
id: number id: number
appKey: string
name: string name: string
status: number status: number
remark: string remark: string

View File

@ -1,8 +1,9 @@
<script lang="tsx"> <script lang="tsx">
import { defineComponent, PropType, ref } from 'vue' import { computed, defineComponent, PropType } from 'vue'
import { isHexColor } from '@/utils/color' import { isHexColor } from '@/utils/color'
import { ElTag } from 'element-plus' import { ElTag } from 'element-plus'
import { DictDataType, getDictOptions } from '@/utils/dict' import { DictDataType, getDictOptions } from '@/utils/dict'
import { isArray, isBoolean, isNumber, isString } from '@/utils/is'
export default defineComponent({ export default defineComponent({
name: 'DictTag', name: 'DictTag',
@ -12,49 +13,78 @@ export default defineComponent({
required: true required: true
}, },
value: { value: {
type: [String, Number, Boolean] as PropType<string | number | boolean>, type: [String, Number, Boolean, Array],
required: true required: true
},
// props.value
separator: {
type: String as PropType<string>,
default: ','
},
// tag 5px el-row gutter
gutter: {
type: String as PropType<string>,
default: '5px'
} }
}, },
setup(props) { setup(props) {
const dictData = ref<DictDataType>() const valueArr: any = computed(() => {
const getDictObj = (dictType: string, value: string) => { // 1. Number Boolean
const dictOptions = getDictOptions(dictType) if (isNumber(props.value) || isBoolean(props.value)) {
dictOptions.forEach((dict: DictDataType) => { return [String(props.value)]
if (dict.value === value) { }
if (dict.colorType + '' === 'default') { // 2. -> props.sepSymbol
dict.colorType = 'info' else if (isString(props.value)) {
} return props.value.split(props.separator)
dictData.value = dict }
} // 3.
}) else if (isArray(props.value)) {
} return props.value.map(String)
const rederDictTag = () => { }
return []
})
const renderDictTag = () => {
if (!props.type) { if (!props.type) {
return null return null
} }
// //
if (props.value === undefined || props.value === null) { if (props.value === undefined || props.value === null || props.value === '') {
return null return null
} }
getDictObj(props.type, props.value.toString()) const dictOptions = getDictOptions(props.type)
//
return ( return (
<ElTag <div
style={dictData.value?.cssClass ? 'color: #fff' : ''} class="dict-tag"
type={dictData.value?.colorType} style={{
color={ display: 'inline-flex',
dictData.value?.cssClass && isHexColor(dictData.value?.cssClass) gap: props.gutter,
? dictData.value?.cssClass justifyContent: 'center',
: '' alignItems: 'center'
} }}
disableTransitions={true}
> >
{dictData.value?.label} {dictOptions.map((dict: DictDataType) => {
</ElTag> if (valueArr.value.includes(dict.value)) {
if (dict.colorType + '' === 'primary' || dict.colorType + '' === 'default') {
dict.colorType = ''
}
return (
//
<ElTag
style={dict?.cssClass ? 'color: #fff' : ''}
type={dict?.colorType || null}
color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''}
disableTransitions={true}
>
{dict?.label}
</ElTag>
)
}
})}
</div>
) )
} }
return () => rederDictTag() return () => renderDictTag()
} }
}) })
</script> </script>

View File

@ -1,35 +1,35 @@
<template> <template>
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef"> <el-scrollbar ref="containerRef" class="z-1 min-h-30px" wrap-class="w-full">
<!-- 商品网格 --> <!-- 商品网格 -->
<div <div
class="grid overflow-x-auto"
:style="{ :style="{
gridGap: `${property.space}px`, gridGap: `${property.space}px`,
gridTemplateColumns, gridTemplateColumns,
width: scrollbarWidth width: scrollbarWidth
}" }"
class="grid overflow-x-auto"
> >
<!-- 商品 --> <!-- 商品 -->
<div <div
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white" v-for="(spu, index) in spuList"
:key="index"
:style="{ :style="{
borderTopLeftRadius: `${property.borderRadiusTop}px`, borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`, borderTopRightRadius: `${property.borderRadiusTop}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`, borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
borderBottomRightRadius: `${property.borderRadiusBottom}px` borderBottomRightRadius: `${property.borderRadiusBottom}px`
}" }"
v-for="(spu, index) in spuList" class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
:key="index"
> >
<!-- 角标 --> <!-- 角标 -->
<div <div
v-if="property.badge.show" v-if="property.badge.show"
class="absolute left-0 top-0 z-1 items-center justify-center" class="absolute left-0 top-0 z-1 items-center justify-center"
> >
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" /> <el-image :src="property.badge.imgUrl" class="h-26px w-38px" fit="cover" />
</div> </div>
<!-- 商品封面图 --> <!-- 商品封面图 -->
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" /> <el-image :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" fit="cover" />
<div <div
:class="[ :class="[
'flex flex-col gap-8px p-8px box-border', 'flex flex-col gap-8px p-8px box-border',
@ -42,8 +42,8 @@
<!-- 商品名称 --> <!-- 商品名称 -->
<div <div
v-if="property.fields.name.show" v-if="property.fields.name.show"
class="truncate text-12px"
:style="{ color: property.fields.name.color }" :style="{ color: property.fields.name.color }"
class="truncate text-12px"
> >
{{ spu.name }} {{ spu.name }}
</div> </div>
@ -51,10 +51,10 @@
<!-- 商品价格 --> <!-- 商品价格 -->
<span <span
v-if="property.fields.price.show" v-if="property.fields.price.show"
class="text-12px"
:style="{ color: property.fields.price.color }" :style="{ color: property.fields.price.color }"
class="text-12px"
> >
{{ spu.price }} {{ fenToYuan(spu.combinationPrice || spu.price || 0) }}
</span> </span>
</div> </div>
</div> </div>
@ -62,10 +62,13 @@
</div> </div>
</el-scrollbar> </el-scrollbar>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import { PromotionCombinationProperty } from './config' import { PromotionCombinationProperty } from './config'
import * as ProductSpuApi from '@/api/mall/product/spu' import * as ProductSpuApi from '@/api/mall/product/spu'
import { Spu } from '@/api/mall/product/spu'
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity' import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
import { CombinationProductVO } from '@/api/mall/promotion/combination/combinationActivity'
import { fenToYuan } from '@/utils'
/** 拼团 */ /** 拼团 */
defineOptions({ name: 'PromotionCombination' }) defineOptions({ name: 'PromotionCombination' })
@ -80,6 +83,13 @@ watch(
const activity = await CombinationActivityApi.getCombinationActivity(props.property.activityId) const activity = await CombinationActivityApi.getCombinationActivity(props.property.activityId)
if (!activity?.spuId) return if (!activity?.spuId) return
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)] spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
//
activity.products.forEach((product: CombinationProductVO) => {
spuList.value.forEach((spu: Spu) => {
// 便
spu.combinationPrice = Math.min(spu.combinationPrice || Infinity, product.combinationPrice) // SPU
})
})
}, },
{ {
immediate: true, immediate: true,
@ -122,4 +132,4 @@ onMounted(() => {
}) })
</script> </script>
<style scoped lang="scss"></style> <style lang="scss" scoped></style>

View File

@ -1,35 +1,35 @@
<template> <template>
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef"> <el-scrollbar ref="containerRef" class="z-1 min-h-30px" wrap-class="w-full">
<!-- 商品网格 --> <!-- 商品网格 -->
<div <div
class="grid overflow-x-auto"
:style="{ :style="{
gridGap: `${property.space}px`, gridGap: `${property.space}px`,
gridTemplateColumns, gridTemplateColumns,
width: scrollbarWidth width: scrollbarWidth
}" }"
class="grid overflow-x-auto"
> >
<!-- 商品 --> <!-- 商品 -->
<div <div
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white" v-for="(spu, index) in spuList"
:key="index"
:style="{ :style="{
borderTopLeftRadius: `${property.borderRadiusTop}px`, borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`, borderTopRightRadius: `${property.borderRadiusTop}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`, borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
borderBottomRightRadius: `${property.borderRadiusBottom}px` borderBottomRightRadius: `${property.borderRadiusBottom}px`
}" }"
v-for="(spu, index) in spuList" class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
:key="index"
> >
<!-- 角标 --> <!-- 角标 -->
<div <div
v-if="property.badge.show" v-if="property.badge.show"
class="absolute left-0 top-0 z-1 items-center justify-center" class="absolute left-0 top-0 z-1 items-center justify-center"
> >
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" /> <el-image :src="property.badge.imgUrl" class="h-26px w-38px" fit="cover" />
</div> </div>
<!-- 商品封面图 --> <!-- 商品封面图 -->
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" /> <el-image :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" fit="cover" />
<div <div
:class="[ :class="[
'flex flex-col gap-8px p-8px box-border', 'flex flex-col gap-8px p-8px box-border',
@ -42,8 +42,8 @@
<!-- 商品名称 --> <!-- 商品名称 -->
<div <div
v-if="property.fields.name.show" v-if="property.fields.name.show"
class="truncate text-12px"
:style="{ color: property.fields.name.color }" :style="{ color: property.fields.name.color }"
class="truncate text-12px"
> >
{{ spu.name }} {{ spu.name }}
</div> </div>
@ -51,10 +51,10 @@
<!-- 商品价格 --> <!-- 商品价格 -->
<span <span
v-if="property.fields.price.show" v-if="property.fields.price.show"
class="text-12px"
:style="{ color: property.fields.price.color }" :style="{ color: property.fields.price.color }"
class="text-12px"
> >
{{ spu.price }} {{ fenToYuan(spu.seckillPrice || spu.price || 0) }}
</span> </span>
</div> </div>
</div> </div>
@ -62,10 +62,13 @@
</div> </div>
</el-scrollbar> </el-scrollbar>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import { PromotionSeckillProperty } from './config' import { PromotionSeckillProperty } from './config'
import * as ProductSpuApi from '@/api/mall/product/spu' import * as ProductSpuApi from '@/api/mall/product/spu'
import { Spu } from '@/api/mall/product/spu'
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity' import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
import { SeckillProductVO } from '@/api/mall/promotion/seckill/seckillActivity'
import { fenToYuan } from '@/utils'
/** 秒杀 */ /** 秒杀 */
defineOptions({ name: 'PromotionSeckill' }) defineOptions({ name: 'PromotionSeckill' })
@ -80,6 +83,13 @@ watch(
const activity = await SeckillActivityApi.getSeckillActivity(props.property.activityId) const activity = await SeckillActivityApi.getSeckillActivity(props.property.activityId)
if (!activity?.spuId) return if (!activity?.spuId) return
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)] spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
//
activity.products.forEach((product: SeckillProductVO) => {
spuList.value.forEach((spu: Spu) => {
spu.seckillPrice = Math.min(spu.seckillPrice || Infinity, product.seckillPrice) // SPU
})
})
}, },
{ {
immediate: true, immediate: true,
@ -122,4 +132,4 @@ onMounted(() => {
}) })
</script> </script>
<style scoped lang="scss"></style> <style lang="scss" scoped></style>

View File

@ -126,6 +126,8 @@ const copyConfig = async () => {
message: ${appStore.getMessage}, message: ${appStore.getMessage},
// //
tagsView: ${appStore.getTagsView}, tagsView: ${appStore.getTagsView},
//
tagsViewImmerse: ${appStore.getTagsViewImmerse},
// //
getTagsViewIcon: ${appStore.getTagsViewIcon}, getTagsViewIcon: ${appStore.getTagsViewIcon},
// logo // logo

View File

@ -73,6 +73,13 @@ const tagsViewChange = (show: boolean) => {
appStore.setTagsView(show) appStore.setTagsView(show)
} }
//
const tagsViewImmerse = ref(appStore.getTagsViewImmerse)
const tagsViewImmerseChange = (immerse: boolean) => {
appStore.setTagsViewImmerse(immerse)
}
// //
const tagsViewIcon = ref(appStore.getTagsViewIcon) const tagsViewIcon = ref(appStore.getTagsViewIcon)
@ -181,6 +188,11 @@ watch(
<ElSwitch v-model="tagsView" @change="tagsViewChange" /> <ElSwitch v-model="tagsView" @change="tagsViewChange" />
</div> </div>
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.tagsViewImmerse') }}</span>
<ElSwitch v-model="tagsViewImmerse" @change="tagsViewImmerseChange" />
</div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.tagsViewIcon') }}</span> <span class="text-14px">{{ t('setting.tagsViewIcon') }}</span>
<ElSwitch v-model="tagsViewIcon" @change="tagsViewIconChange" /> <ElSwitch v-model="tagsViewIcon" @change="tagsViewIconChange" />

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, watch, computed, unref, ref, nextTick } from 'vue' import { computed, nextTick, onMounted, ref, unref, watch } from 'vue'
import { useRouter } from 'vue-router'
import type { RouteLocationNormalizedLoaded, RouterLinkProps } from 'vue-router' import type { RouteLocationNormalizedLoaded, RouterLinkProps } from 'vue-router'
import { useRouter } from 'vue-router'
import { usePermissionStore } from '@/store/modules/permission' import { usePermissionStore } from '@/store/modules/permission'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
@ -33,6 +33,8 @@ const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
const appStore = useAppStore() const appStore = useAppStore()
const tagsViewImmerse = computed(() => appStore.getTagsViewImmerse)
const tagsViewIcon = computed(() => appStore.getTagsViewIcon) const tagsViewIcon = computed(() => appStore.getTagsViewIcon)
const isDark = computed(() => appStore.getIsDark) const isDark = computed(() => appStore.getIsDark)
@ -266,21 +268,33 @@ watch(
class="relative w-full flex bg-[#fff] dark:bg-[var(--el-bg-color)]" class="relative w-full flex bg-[#fff] dark:bg-[var(--el-bg-color)]"
> >
<span <span
:class="`${prefixCls}__tool ${prefixCls}__tool--first`" :class="tagsViewImmerse ? '' : `${prefixCls}__tool ${prefixCls}__tool--first`"
class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center" class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
@click="move(-200)" @click="move(-200)"
> >
<Icon <Icon
icon="ep:d-arrow-left"
color="var(--el-text-color-placeholder)"
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'" :hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
color="var(--el-text-color-placeholder)"
icon="ep:d-arrow-left"
/> />
</span> </span>
<div class="flex-1 overflow-hidden"> <div class="flex-1 overflow-hidden">
<ElScrollbar ref="scrollbarRef" class="h-full" @scroll="scroll"> <ElScrollbar ref="scrollbarRef" class="h-full" @scroll="scroll">
<div class="h-full flex"> <div class="h-full flex">
<ContextMenu <ContextMenu
v-for="item in visitedViews"
:key="item.fullPath"
:ref="itemRefs.set" :ref="itemRefs.set"
:class="[
`${prefixCls}__item`,
tagsViewImmerse ? `${prefixCls}__item--immerse` : '',
tagsViewIcon ? `${prefixCls}__item--icon` : '',
tagsViewImmerse && tagsViewIcon ? `${prefixCls}__item--immerse--icon` : '',
item?.meta?.affix ? `${prefixCls}__item--affix` : '',
{
'is-active': isActive(item)
}
]"
:schema="[ :schema="[
{ {
icon: 'ep:refresh', icon: 'ep:refresh',
@ -338,41 +352,33 @@ watch(
} }
} }
]" ]"
v-for="item in visitedViews"
:key="item.fullPath"
:tag-item="item" :tag-item="item"
:class="[
`${prefixCls}__item`,
item?.meta?.affix ? `${prefixCls}__item--affix` : '',
{
'is-active': isActive(item)
}
]"
@visible-change="visibleChange" @visible-change="visibleChange"
> >
<div> <div>
<router-link :ref="tagLinksRefs.set" :to="{ ...item }" custom v-slot="{ navigate }"> <router-link :ref="tagLinksRefs.set" v-slot="{ navigate }" :to="{ ...item }" custom>
<div <div
:class="`h-full flex items-center justify-center whitespace-nowrap pl-15px ${prefixCls}__item--label`"
@click="navigate" @click="navigate"
class="h-full flex items-center justify-center whitespace-nowrap pl-15px"
> >
<Icon <Icon
v-if=" v-if="
item?.matched && tagsViewIcon &&
item?.matched[1] && (item?.meta?.icon ||
item?.matched[1]?.meta?.icon && (item?.matched &&
tagsViewIcon item.matched[0] &&
item.matched[item.matched.length - 1].meta?.icon))
" "
:icon="item?.matched[1]?.meta?.icon" :icon="item?.meta?.icon || item.matched[item.matched.length - 1].meta.icon"
:size="12" :size="12"
class="mr-5px" class="mr-5px"
/> />
{{ t(item?.meta?.title as string) }} {{ t(item?.meta?.title as string) }}
<Icon <Icon
:class="`${prefixCls}__item--close`" :class="`${prefixCls}__item--close`"
:size="12"
color="#333" color="#333"
icon="ep:close" icon="ep:close"
:size="12"
@click.prevent.stop="closeSelectedTag(item)" @click.prevent.stop="closeSelectedTag(item)"
/> />
</div> </div>
@ -383,29 +389,28 @@ watch(
</ElScrollbar> </ElScrollbar>
</div> </div>
<span <span
:class="`${prefixCls}__tool`" :class="tagsViewImmerse ? '' : `${prefixCls}__tool`"
class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center" class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
@click="move(200)" @click="move(200)"
> >
<Icon <Icon
icon="ep:d-arrow-right"
color="var(--el-text-color-placeholder)"
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'" :hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
color="var(--el-text-color-placeholder)"
icon="ep:d-arrow-right"
/> />
</span> </span>
<span <span
:class="`${prefixCls}__tool`" :class="tagsViewImmerse ? '' : `${prefixCls}__tool`"
class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center" class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
@click="refreshSelectedTag(selectedTag)" @click="refreshSelectedTag(selectedTag)"
> >
<Icon <Icon
icon="ep:refresh-right"
color="var(--el-text-color-placeholder)"
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'" :hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
color="var(--el-text-color-placeholder)"
icon="ep:refresh-right"
/> />
</span> </span>
<ContextMenu <ContextMenu
trigger="click"
:schema="[ :schema="[
{ {
icon: 'ep:refresh', icon: 'ep:refresh',
@ -457,15 +462,16 @@ watch(
} }
} }
]" ]"
trigger="click"
> >
<span <span
:class="`${prefixCls}__tool`" :class="tagsViewImmerse ? '' : `${prefixCls}__tool`"
class="block h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center" class="block h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
> >
<Icon <Icon
icon="ep:menu"
color="var(--el-text-color-placeholder)"
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'" :hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
color="var(--el-text-color-placeholder)"
icon="ep:menu"
/> />
</span> </span>
</ContextMenu> </ContextMenu>
@ -511,7 +517,7 @@ $prefix-cls: #{$namespace}-tags-view;
position: relative; position: relative;
top: 2px; top: 2px;
height: calc(100% - 6px); height: calc(100% - 6px);
padding-right: 25px; padding-right: 15px;
margin-left: 4px; margin-left: 4px;
font-size: 12px; font-size: 12px;
cursor: pointer; cursor: pointer;
@ -525,6 +531,7 @@ $prefix-cls: #{$namespace}-tags-view;
display: none; display: none;
transform: translate(0, -50%); transform: translate(0, -50%);
} }
&:not(.#{$prefix-cls}__item--affix):hover { &:not(.#{$prefix-cls}__item--affix):hover {
.#{$prefix-cls}__item--close { .#{$prefix-cls}__item--close {
display: block; display: block;
@ -532,6 +539,10 @@ $prefix-cls: #{$namespace}-tags-view;
} }
} }
&__item--icon {
padding-right: 20px;
}
&__item:not(.is-active) { &__item:not(.is-active) {
&:hover { &:hover {
color: var(--el-color-primary); color: var(--el-color-primary);
@ -542,12 +553,47 @@ $prefix-cls: #{$namespace}-tags-view;
color: var(--el-color-white); color: var(--el-color-white);
background-color: var(--el-color-primary); background-color: var(--el-color-primary);
border: 1px solid var(--el-color-primary); border: 1px solid var(--el-color-primary);
.#{$prefix-cls}__item--close { .#{$prefix-cls}__item--close {
:deep(span) { :deep(span) {
color: var(--el-color-white) !important; color: var(--el-color-white) !important;
} }
} }
} }
&__item--immerse {
top: 3px;
padding-right: 35px;
margin: 0 -10px;
border: 1px solid transparent;
-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;
.#{$prefix-cls}__item--label {
padding-left: 35px;
}
.#{$prefix-cls}__item--close {
right: 20px;
}
}
&__item--immerse--icon {
padding-right: 35px;
}
&__item--immerse:not(.is-active) {
&:hover {
color: var(--el-color-white);
background-color: var(--el-color-primary);
.#{$prefix-cls}__item--close {
:deep(span) {
color: var(--el-color-white) !important;
}
}
}
}
} }
.dark { .dark {
@ -574,6 +620,7 @@ $prefix-cls: #{$namespace}-tags-view;
color: var(--el-color-white); color: var(--el-color-white);
background-color: var(--el-color-primary); background-color: var(--el-color-primary);
border: 1px solid var(--el-color-primary); border: 1px solid var(--el-color-primary);
.#{$prefix-cls}__item--close { .#{$prefix-cls}__item--close {
:deep(span) { :deep(span) {
color: var(--el-color-white) !important; color: var(--el-color-white) !important;

View File

@ -92,6 +92,7 @@ export default {
localeIcon: '多语言图标', localeIcon: '多语言图标',
messageIcon: '消息图标', messageIcon: '消息图标',
tagsView: '标签页', tagsView: '标签页',
tagsViewImmerse: '标签页沉浸',
logo: '标志', logo: '标志',
greyMode: '灰色模式', greyMode: '灰色模式',
fixedHeader: '固定头部', fixedHeader: '固定头部',

View File

@ -589,11 +589,20 @@ const remainingRouter: AppRouteRecordRaw[] = [
meta: { meta: {
title: '绘图作品', title: '绘图作品',
icon: 'ep:home-filled', icon: 'ep:home-filled',
noCache: false, noCache: false
affix: true
} }
} }
] ]
},
{
path: '/:pathMatch(.*)*',
component: () => import('@/views/Error/404.vue'),
name: '',
meta: {
title: '404',
hidden: true,
breadcrumb: false
}
} }
] ]

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { store } from '../index' import { store } from '../index'
import { setCssVar, humpToUnderline } from '@/utils' import { humpToUnderline, setCssVar } from '@/utils'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { ElementPlusSize } from '@/types/elementPlus' import { ElementPlusSize } from '@/types/elementPlus'
@ -21,6 +21,7 @@ interface AppState {
locale: boolean locale: boolean
message: boolean message: boolean
tagsView: boolean tagsView: boolean
tagsViewImmerse: boolean
tagsViewIcon: boolean tagsViewIcon: boolean
logo: boolean logo: boolean
fixedHeader: boolean fixedHeader: boolean
@ -58,6 +59,7 @@ export const useAppStore = defineStore('app', {
locale: true, // 多语言图标 locale: true, // 多语言图标
message: true, // 消息图标 message: true, // 消息图标
tagsView: true, // 标签页 tagsView: true, // 标签页
tagsViewImmerse: false, // 标签页沉浸
tagsViewIcon: true, // 是否显示标签图标 tagsViewIcon: true, // 是否显示标签图标
logo: true, // logo logo: true, // logo
fixedHeader: true, // 固定toolheader fixedHeader: true, // 固定toolheader
@ -131,6 +133,9 @@ export const useAppStore = defineStore('app', {
getTagsView(): boolean { getTagsView(): boolean {
return this.tagsView return this.tagsView
}, },
getTagsViewImmerse(): boolean {
return this.tagsViewImmerse
},
getTagsViewIcon(): boolean { getTagsViewIcon(): boolean {
return this.tagsViewIcon return this.tagsViewIcon
}, },
@ -208,6 +213,9 @@ export const useAppStore = defineStore('app', {
setTagsView(tagsView: boolean) { setTagsView(tagsView: boolean) {
this.tagsView = tagsView this.tagsView = tagsView
}, },
setTagsViewImmerse(tagsViewImmerse: boolean) {
this.tagsViewImmerse = tagsViewImmerse
},
setTagsViewIcon(tagsViewIcon: boolean) { setTagsViewIcon(tagsViewIcon: boolean) {
this.tagsViewIcon = tagsViewIcon this.tagsViewIcon = tagsViewIcon
}, },

View File

@ -40,10 +40,12 @@ export const usePermissionStore = defineStore('permission', {
} }
const routerMap: AppRouteRecordRaw[] = generateRoute(res) const routerMap: AppRouteRecordRaw[] = generateRoute(res)
// 动态路由404一定要放到最后面 // 动态路由404一定要放到最后面
// preschoolervue-router@4以后已支持静态404路由此处可不再追加
this.addRouters = routerMap.concat([ this.addRouters = routerMap.concat([
{ {
path: '/:path(.*)*', path: '/:path(.*)*',
redirect: '/404', // redirect: '/404',
component: () => import('@/views/Error/404.vue'),
name: '404Page', name: '404Page',
meta: { meta: {
hidden: true, hidden: true,

View File

@ -1,5 +1,6 @@
@import './var.css'; @import './var.css';
@import './FormCreate/index.scss'; @import './FormCreate/index.scss';
@import './theme.scss';
@import 'element-plus/theme-chalk/dark/css-vars.css'; @import 'element-plus/theme-chalk/dark/css-vars.css';
.reset-margin [class*='el-icon'] + span { .reset-margin [class*='el-icon'] + span {

View File

@ -4,3 +4,14 @@
// .dark .dark\:text-color { // .dark .dark\:text-color {
// color: rgba(255, 255, 255, var(--dark-text-color)); // color: rgba(255, 255, 255, var(--dark-text-color));
// } // }
// 登录页
.dark .login-form {
.el-divider__text {
background-color: var(--login-bg-color);
}
.el-card {
background-color: var(--login-bg-color);
}
}

View File

@ -5,7 +5,7 @@
> >
<div class="relative mx-auto h-full flex"> <div class="relative mx-auto h-full flex">
<div <div
:class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden`" :class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden overflow-x-hidden overflow-y-auto`"
> >
<!-- 左上角的 logo + 系统标题 --> <!-- 左上角的 logo + 系统标题 -->
<div class="relative flex items-center text-white"> <div class="relative flex items-center text-white">
@ -27,7 +27,9 @@
</TransitionGroup> </TransitionGroup>
</div> </div>
</div> </div>
<div class="relative flex-1 p-30px dark:bg-[var(--login-bg-color)] lt-sm:p-10px"> <div
class="relative flex-1 p-30px dark:bg-[var(--login-bg-color)] lt-sm:p-10px overflow-x-hidden overflow-y-auto"
>
<!-- 右上角的主题语言选择 --> <!-- 右上角的主题语言选择 -->
<div <div
class="flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end" class="flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end"
@ -36,7 +38,7 @@
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" /> <img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span> <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
</div> </div>
<div class="flex items-center justify-end space-x-10px"> <div class="flex items-center justify-end space-x-10px h-48px">
<ThemeSwitch /> <ThemeSwitch />
<LocaleDropdown class="dark:text-white lt-xl:text-white" /> <LocaleDropdown class="dark:text-white lt-xl:text-white" />
</div> </div>
@ -44,7 +46,7 @@
<!-- 右边的登录界面 --> <!-- 右边的登录界面 -->
<Transition appear enter-active-class="animate__animated animate__bounceInRight"> <Transition appear enter-active-class="animate__animated animate__bounceInRight">
<div <div
class="m-auto h-full w-[100%] flex items-center at-2xl:max-w-500px at-lg:max-w-500px at-md:max-w-500px at-xl:max-w-500px" class="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"
> >
<!-- 账号登录 --> <!-- 账号登录 -->
<LoginForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" /> <LoginForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />

View File

@ -1,11 +1,11 @@
<template> <template>
<div <div
:class="prefixCls" :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" class="relative h-[100%] lt-md:px-10px lt-sm:px-10px lt-xl:px-10px lt-xl:px-10px"
> >
<div class="relative mx-auto h-full flex"> <div class="relative mx-auto h-full flex">
<div <div
:class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden`" :class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden overflow-x-hidden overflow-y-auto`"
> >
<!-- 左上角的 logo + 系统标题 --> <!-- 左上角的 logo + 系统标题 -->
<div class="relative flex items-center text-white"> <div class="relative flex items-center text-white">
@ -27,7 +27,9 @@
</TransitionGroup> </TransitionGroup>
</div> </div>
</div> </div>
<div class="relative flex-1 p-30px dark:bg-[var(--login-bg-color)] lt-sm:p-10px"> <div
class="relative flex-1 p-30px dark:bg-[var(--login-bg-color)] lt-sm:p-10px overflow-x-hidden overflow-y-auto"
>
<!-- 右上角的主题语言选择 --> <!-- 右上角的主题语言选择 -->
<div <div
class="flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end" class="flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end"
@ -36,7 +38,7 @@
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" /> <img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span> <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
</div> </div>
<div class="flex items-center justify-end space-x-10px"> <div class="flex items-center justify-end space-x-10px h-48px">
<ThemeSwitch /> <ThemeSwitch />
<LocaleDropdown class="dark:text-white lt-xl:text-white" /> <LocaleDropdown class="dark:text-white lt-xl:text-white" />
</div> </div>
@ -44,7 +46,7 @@
<!-- 右边的登录界面 --> <!-- 右边的登录界面 -->
<Transition appear enter-active-class="animate__animated animate__bounceInRight"> <Transition appear enter-active-class="animate__animated animate__bounceInRight">
<div <div
class="m-auto h-full w-[100%] flex items-center at-2xl:max-w-500px at-lg:max-w-500px at-md:max-w-500px at-xl:max-w-500px" class="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"
> >
<!-- 账号登录 --> <!-- 账号登录 -->
<el-form <el-form
@ -112,9 +114,9 @@
</el-checkbox> </el-checkbox>
</el-col> </el-col>
<el-col :offset="6" :span="12"> <el-col :offset="6" :span="12">
<el-link style="float: right" type="primary">{{ <el-link style="float: right" type="primary"
t('login.forgetPassword') >{{ t('login.forgetPassword') }}
}}</el-link> </el-link>
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
@ -274,10 +276,11 @@ const handleLogin = async (params) => {
const code = route?.query?.code as string const code = route?.query?.code as string
const state = route?.query?.state as string const state = route?.query?.state as string
const loginDataLoginForm = { ...loginData.loginForm }
const res = await LoginApi.login({ const res = await LoginApi.login({
// //
username: loginData.loginForm.username, username: loginDataLoginForm.username,
password: loginData.loginForm.password, password: loginDataLoginForm.password,
captchaVerification: params.captchaVerification, captchaVerification: params.captchaVerification,
// //
socialCode: code, socialCode: code,
@ -292,8 +295,8 @@ const handleLogin = async (params) => {
text: '正在加载系统中...', text: '正在加载系统中...',
background: 'rgba(0, 0, 0, 0.7)' background: 'rgba(0, 0, 0, 0.7)'
}) })
if (loginData.loginForm.rememberMe) { if (loginDataLoginForm.rememberMe) {
authUtil.setLoginForm(loginData.loginForm) authUtil.setLoginForm(loginDataLoginForm)
} else { } else {
authUtil.removeLoginForm() authUtil.removeLoginForm()
} }

View File

@ -249,8 +249,9 @@ const handleLogin = async (params) => {
if (!data) { if (!data) {
return return
} }
loginData.loginForm.captchaVerification = params.captchaVerification const loginDataLoginForm = { ...loginData.loginForm }
const res = await LoginApi.login(loginData.loginForm) loginDataLoginForm.captchaVerification = params.captchaVerification
const res = await LoginApi.login(loginDataLoginForm)
if (!res) { if (!res) {
return return
} }
@ -259,8 +260,8 @@ const handleLogin = async (params) => {
text: '正在加载系统中...', text: '正在加载系统中...',
background: 'rgba(0, 0, 0, 0.7)' background: 'rgba(0, 0, 0, 0.7)'
}) })
if (loginData.loginForm.rememberMe) { if (loginDataLoginForm.rememberMe) {
authUtil.setLoginForm(loginData.loginForm) authUtil.setLoginForm(loginDataLoginForm)
} else { } else {
authUtil.removeLoginForm() authUtil.removeLoginForm()
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<el-row v-show="getShow" style="margin-right: -10px; margin-left: -10px"> <el-row v-show="getShow" class="login-form" style="margin-right: -10px; margin-left: -10px">
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<LoginFormTitle style="width: 100%" /> <LoginFormTitle style="width: 100%" />
</el-col> </el-col>

View File

@ -3,7 +3,7 @@
v-show="getShow" v-show="getShow"
:rules="rules" :rules="rules"
:schema="schema" :schema="schema"
class="dark:(border-1 border-[var(--el-border-color)] border-solid)" class="w-[100%] dark:(border-1 border-[var(--el-border-color)] border-solid)"
hide-required-asterisk hide-required-asterisk
label-position="top" label-position="top"
size="large" size="large"

View File

@ -4,7 +4,7 @@
<h3 class="m-0 px-7 shrink-0 flex items-center justify-between"> <h3 class="m-0 px-7 shrink-0 flex items-center justify-between">
<span>思维导图预览</span> <span>思维导图预览</span>
<!-- 展示在右上角 --> <!-- 展示在右上角 -->
<el-button type="primary" v-show="isEnd" @click="downloadImage" size="small"> <el-button v-show="isEnd" size="small" type="primary" @click="downloadImage">
<template #icon> <template #icon>
<Icon icon="ph:copy-bold" /> <Icon icon="ph:copy-bold" />
</template> </template>
@ -20,14 +20,14 @@
</div> </div>
<div ref="mindMapRef" class="wh-full"> <div ref="mindMapRef" class="wh-full">
<svg ref="svgRef" class="w-full" :style="{ height: `${contentAreaHeight}px` }" /> <svg ref="svgRef" :style="{ height: `${contentAreaHeight}px` }" class="w-full" />
<div ref="toolBarRef" class="absolute bottom-[10px] right-5"></div> <div ref="toolBarRef" class="absolute bottom-[10px] right-5"></div>
</div> </div>
</div> </div>
</el-card> </el-card>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import { Markmap } from 'markmap-view' import { Markmap } from 'markmap-view'
import { Transformer } from 'markmap-lib' import { Transformer } from 'markmap-lib'
import { Toolbar } from 'markmap-toolbar' import { Toolbar } from 'markmap-toolbar'
@ -43,7 +43,7 @@ const props = defineProps<{
isGenerating: boolean // isGenerating: boolean //
isStart: boolean // html isStart: boolean // html
}>() }>()
const contentRef = ref<HTMLDivElement>() // header const contentRef = ref<HTMLDivElement>() // header
const mdContainerRef = ref<HTMLDivElement>() // markdown const mdContainerRef = ref<HTMLDivElement>() // markdown
const mindMapRef = ref<HTMLDivElement>() // const mindMapRef = ref<HTMLDivElement>() //
const svgRef = ref<SVGElement>() // svg const svgRef = ref<SVGElement>() // svg
@ -106,8 +106,7 @@ const processContent = (text: string) => {
return arr.join('\n') return arr.join('\n')
} }
/** 下载图片 */ /** 下载图片download SVG to png file */
// download SVG to png file
const downloadImage = () => { const downloadImage = () => {
const svgElement = mindMapRef.value const svgElement = mindMapRef.value
// SVG // SVG
@ -138,6 +137,7 @@ defineExpose({
height: 0; height: 0;
} }
} }
.my-card { .my-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -150,13 +150,16 @@ defineExpose({
@extend .hide-scroll-bar; @extend .hide-scroll-bar;
} }
} }
// markmaptool // markmaptool
:deep(.markmap) { :deep(.markmap) {
width: 100%; width: 100%;
} }
:deep(.mm-toolbar-brand) { :deep(.mm-toolbar-brand) {
display: none; display: none;
} }
:deep(.mm-toolbar) { :deep(.mm-toolbar) {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -3,9 +3,9 @@
<!--表单区域--> <!--表单区域-->
<Left <Left
ref="leftRef" ref="leftRef"
:is-generating="isGenerating"
@submit="submit" @submit="submit"
@direct-generate="directGenerate" @direct-generate="directGenerate"
:is-generating="isGenerating"
/> />
<!--右边生成思维导图区域--> <!--右边生成思维导图区域-->
<Right <Right
@ -18,7 +18,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import Left from './components/Left.vue' import Left from './components/Left.vue'
import Right from './components/Right.vue' import Right from './components/Right.vue'
import { AiMindMapApi, AiMindMapGenerateReqVO } from '@/api/ai/mindmap' import { AiMindMapApi, AiMindMapGenerateReqVO } from '@/api/ai/mindmap'
@ -40,7 +40,7 @@ const rightRef = ref<InstanceType<typeof Right>>() // 右边组件
/** 使用已有内容直接生成 **/ /** 使用已有内容直接生成 **/
const directGenerate = (existPrompt: string) => { const directGenerate = (existPrompt: string) => {
isEnd.value = false // falsetruewatch isEnd.value = false // false true watch
generatedContent.value = existPrompt generatedContent.value = existPrompt
isEnd.value = true isEnd.value = true
} }

View File

@ -0,0 +1,195 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px"
>
<el-form-item label="用户编号" prop="userId">
<el-select
v-model="queryParams.userId"
class="!w-240px"
clearable
placeholder="请输入用户编号"
>
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="提示词" prop="prompt">
<el-input
v-model="queryParams.prompt"
class="!w-240px"
clearable
placeholder="请输入提示词"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" fixed="left" label="编号" prop="id" width="180" />
<el-table-column align="center" label="用户" prop="userId" width="180">
<template #default="scope">
<span>{{ userList.find((item) => item.id === scope.row.userId)?.nickname }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="提示词" prop="prompt" width="180" />
<el-table-column align="center" label="思维导图" min-width="300" prop="generatedContent" />
<el-table-column align="center" label="模型" prop="model" width="180" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="createTime"
width="180px"
/>
<el-table-column align="center" label="错误信息" prop="errorMessage" />
<el-table-column align="center" fixed="right" label="操作" width="120">
<template #default="scope">
<el-button link type="primary" @click="openPreview(scope.row)"> 预览</el-button>
<el-button
v-hasPermi="['ai:mind-map:delete']"
link
type="danger"
@click="handleDelete(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
<!-- 思维导图的预览 -->
<el-drawer v-model="previewVisible" :with-header="false" size="800px">
<Right
v-if="previewVisible2"
:generatedContent="previewContent"
:isEnd="true"
:isGenerating="false"
:isStart="false"
/>
</el-drawer>
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import { AiMindMapApi, MindMapVO } from '@/api/ai/mindmap'
import * as UserApi from '@/api/system/user'
import Right from '@/views/ai/mindmap/index/components/Right.vue'
/** AI 思维导图 列表 */
defineOptions({ name: 'AiMindMapManager' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<MindMapVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
userId: undefined,
prompt: undefined,
createTime: []
})
const queryFormRef = ref() //
const userList = ref<UserApi.UserVO[]>([]) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await AiMindMapApi.getMindMapPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await AiMindMapApi.deleteMindMap(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 预览操作按钮 */
const previewVisible = ref(false) // drawer
const previewVisible2 = ref(false) // right
const previewContent = ref('')
const openPreview = async (row: MindMapVO) => {
previewVisible2.value = false
previewVisible.value = true
// drawer right width
await nextTick()
previewVisible2.value = true
previewContent.value = row.generatedContent
}
/** 初始化 **/
onMounted(async () => {
getList()
//
userList.value = await UserApi.getSimpleUserList()
})
</script>

View File

@ -2,8 +2,8 @@
<!-- 定义 tab 组件撰写/回复等 --> <!-- 定义 tab 组件撰写/回复等 -->
<DefineTab v-slot="{ active, text, itemClick }"> <DefineTab v-slot="{ active, text, itemClick }">
<span <span
class="inline-block w-1/2 rounded-full cursor-pointer text-center leading-[30px] relative z-1 text-[5C6370] hover:text-black"
:class="active ? 'text-black shadow-md' : 'hover:bg-[#DDDFE3]'" :class="active ? 'text-black shadow-md' : 'hover:bg-[#DDDFE3]'"
class="inline-block w-1/2 rounded-full cursor-pointer text-center leading-[30px] relative z-1 text-[5C6370] hover:text-black"
@click="itemClick" @click="itemClick"
> >
{{ text }} {{ text }}
@ -14,9 +14,9 @@
<h3 class="mt-5 mb-3 flex items-center justify-between text-[14px]"> <h3 class="mt-5 mb-3 flex items-center justify-between text-[14px]">
<span>{{ label }}</span> <span>{{ label }}</span>
<span <span
@click="hintClick"
v-if="hint" v-if="hint"
class="flex items-center text-[12px] text-[#846af7] cursor-pointer select-none" class="flex items-center text-[12px] text-[#846af7] cursor-pointer select-none"
@click="hintClick"
> >
<Icon icon="ep:question-filled" /> <Icon icon="ep:question-filled" />
{{ hint }} {{ hint }}
@ -29,17 +29,17 @@
<div class="w-full pt-2 bg-[#f5f7f9] flex justify-center"> <div class="w-full pt-2 bg-[#f5f7f9] flex justify-center">
<div class="w-[303px] rounded-full bg-[#DDDFE3] p-1 z-10"> <div class="w-[303px] rounded-full bg-[#DDDFE3] p-1 z-10">
<div <div
class="flex items-center relative after:content-[''] after:block after:bg-white after:h-[30px] after:w-1/2 after:absolute after:top-0 after:left-0 after:transition-transform after:rounded-full"
:class=" :class="
selectedTab === AiWriteTypeEnum.REPLY && 'after:transform after:translate-x-[100%]' selectedTab === AiWriteTypeEnum.REPLY && 'after:transform after:translate-x-[100%]'
" "
class="flex items-center relative after:content-[''] after:block after:bg-white after:h-[30px] after:w-1/2 after:absolute after:top-0 after:left-0 after:transition-transform after:rounded-full"
> >
<ReuseTab <ReuseTab
v-for="tab in tabs" v-for="tab in tabs"
:key="tab.value" :key="tab.value"
:text="tab.text"
:active="tab.value === selectedTab" :active="tab.value === selectedTab"
:itemClick="() => switchTab(tab.value)" :itemClick="() => switchTab(tab.value)"
:text="tab.text"
/> />
</div> </div>
</div> </div>
@ -49,36 +49,36 @@
> >
<div> <div>
<template v-if="selectedTab === 1"> <template v-if="selectedTab === 1">
<ReuseLabel label="写作内容" hint="示例" :hint-click="() => example('write')" /> <ReuseLabel :hint-click="() => example('write')" hint="示例" label="写作内容" />
<el-input <el-input
type="textarea"
:rows="5"
:maxlength="500"
v-model="formData.prompt" v-model="formData.prompt"
:maxlength="500"
:rows="5"
placeholder="请输入写作内容" placeholder="请输入写作内容"
showWordLimit showWordLimit
type="textarea"
/> />
</template> </template>
<template v-else> <template v-else>
<ReuseLabel label="原文" hint="示例" :hint-click="() => example('reply')" /> <ReuseLabel :hint-click="() => example('reply')" hint="示例" label="原文" />
<el-input <el-input
type="textarea"
:rows="5"
:maxlength="500"
v-model="formData.originalContent" v-model="formData.originalContent"
:maxlength="500"
:rows="5"
placeholder="请输入原文" placeholder="请输入原文"
showWordLimit showWordLimit
type="textarea"
/> />
<ReuseLabel label="回复内容" /> <ReuseLabel label="回复内容" />
<el-input <el-input
type="textarea"
:rows="5"
:maxlength="500"
v-model="formData.prompt" v-model="formData.prompt"
:maxlength="500"
:rows="5"
placeholder="请输入回复内容" placeholder="请输入回复内容"
showWordLimit showWordLimit
type="textarea"
/> />
</template> </template>
@ -93,18 +93,18 @@
<div class="flex items-center justify-center mt-3"> <div class="flex items-center justify-center mt-3">
<el-button :disabled="isWriting" @click="reset">重置</el-button> <el-button :disabled="isWriting" @click="reset">重置</el-button>
<el-button :loading="isWriting" @click="submit" color="#846af7">生成</el-button> <el-button :loading="isWriting" color="#846af7" @click="submit">生成</el-button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import { createReusableTemplate } from '@vueuse/core' import { createReusableTemplate } from '@vueuse/core'
import { ref } from 'vue' import { ref } from 'vue'
import Tag from './Tag.vue' import Tag from './Tag.vue'
import { WriteVO } from 'src/api/ai/write' import { WriteVO } from '@/api/ai/write'
import { omit } from 'lodash-es' import { omit } from 'lodash-es'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { AiWriteTypeEnum, WriteExample } from '@/views/ai/utils/constants' import { AiWriteTypeEnum, WriteExample } from '@/views/ai/utils/constants'

View File

@ -11,9 +11,9 @@
@click="handleClick(item.routerName)" @click="handleClick(item.routerName)"
> >
<CountTo <CountTo
:prefix="item.prefix"
:end-val="item.value"
:decimals="item.decimals" :decimals="item.decimals"
:end-val="item.value"
:prefix="item.prefix"
class="text-3xl" class="text-3xl"
/> />
<span class="text-center">{{ item.name }}</span> <span class="text-center">{{ item.name }}</span>
@ -53,10 +53,18 @@ const data = reactive({
/** 查询订单数据 */ /** 查询订单数据 */
const getOrderData = async () => { const getOrderData = async () => {
const orderCount = await TradeStatisticsApi.getOrderCount() const orderCount = await TradeStatisticsApi.getOrderCount()
data.orderUndelivered.value = orderCount.undelivered if (orderCount.undelivered != null) {
data.orderAfterSaleApply.value = orderCount.afterSaleApply data.orderUndelivered.value = orderCount.undelivered
data.orderWaitePickUp.value = orderCount.pickUp }
data.withdrawAuditing.value = orderCount.auditingWithdraw if (orderCount.afterSaleApply != null) {
data.orderAfterSaleApply.value = orderCount.afterSaleApply
}
if (orderCount.pickUp != null) {
data.orderWaitePickUp.value = orderCount.pickUp
}
if (orderCount.auditingWithdraw != null) {
data.withdrawAuditing.value = orderCount.auditingWithdraw
}
} }
/** 查询商品数据 */ /** 查询商品数据 */
@ -83,6 +91,13 @@ const handleClick = (routerName: string) => {
router.push({ name: routerName }) router.push({ name: routerName })
} }
/** 激活时 */
onActivated(() => {
getOrderData()
getProductData()
getWalletRechargeData()
})
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
getOrderData() getOrderData()

View File

@ -24,7 +24,7 @@
> >
<template #default="{ row }"> <template #default="{ row }">
<span style="font-weight: bold; color: #40aaff"> <span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }} {{ row.properties?.[index]?.valueName }}
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
@ -168,7 +168,7 @@
> >
<template #default="{ row }"> <template #default="{ row }">
<span style="font-weight: bold; color: #40aaff"> <span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }} {{ row.properties?.[index]?.valueName }}
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
@ -248,7 +248,7 @@
> >
<template #default="{ row }"> <template #default="{ row }">
<span style="font-weight: bold; color: #40aaff"> <span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }} {{ row.properties?.[index]?.valueName }}
</span> </span>
</template> </template>
</el-table-column> </el-table-column>

View File

@ -18,16 +18,28 @@
> >
{{ value.name }} {{ value.name }}
</el-tag> </el-tag>
<el-input <el-select
v-show="inputVisible(index)" v-show="inputVisible(index)"
:id="`input${index}`" :id="`input${index}`"
:ref="setInputRef" :ref="setInputRef"
v-model="inputValue" v-model="inputValue"
class="!w-20" :reserve-keyword="false"
allow-create
class="!w-30"
default-first-option
filterable
size="small" size="small"
@blur="handleInputConfirm(index, item.id)" @blur="handleInputConfirm(index, item.id)"
@change="handleInputConfirm(index, item.id)"
@keyup.enter="handleInputConfirm(index, item.id)" @keyup.enter="handleInputConfirm(index, item.id)"
/> >
<el-option
v-for="item2 in attributeOptions"
:key="item2.id"
:label="item2.name"
:value="item2.name"
/>
</el-select>
<el-button <el-button
v-show="!inputVisible(index)" v-show="!inputVisible(index)"
class="button-new-tag ml-1" class="button-new-tag ml-1"
@ -42,7 +54,6 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ElInput } from 'element-plus'
import * as PropertyApi from '@/api/mall/product/property' import * as PropertyApi from '@/api/mall/product/property'
import { PropertyAndValues } from '@/views/mall/product/spu/components' import { PropertyAndValues } from '@/views/mall/product/spu/components'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
@ -63,11 +74,12 @@ const inputRef = ref<any[]>([]) //标签输入框Ref
const setInputRef = (el: any) => { const setInputRef = (el: any) => {
if (el === null || typeof el === 'undefined') return if (el === null || typeof el === 'undefined') return
// id // id
if (!inputRef.value.some((item) => item.input?.attributes.id === el.input?.attributes.id)) { if (!inputRef.value.some((item) => item.inputRef?.attributes.id === el.inputRef?.attributes.id)) {
inputRef.value.push(el) inputRef.value.push(el)
} }
} }
const attributeList = ref<PropertyAndValues[]>([]) // const attributeList = ref<PropertyAndValues[]>([]) //
const attributeOptions = ref([] as PropertyApi.PropertyValueVO[]) //
const props = defineProps({ const props = defineProps({
propertyList: { propertyList: {
type: Array, type: Array,
@ -100,16 +112,36 @@ const handleCloseProperty = (index: number) => {
} }
/** 显示输入框并获取焦点 */ /** 显示输入框并获取焦点 */
const showInput = async (index) => { const showInput = async (index: number) => {
attributeIndex.value = index attributeIndex.value = index
inputRef.value[index].focus() inputRef.value[index].focus()
//
await getAttributeOptions(attributeList.value[index].id)
} }
/** 输入框失去焦点或点击回车时触发 */ /** 输入框失去焦点或点击回车时触发 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success']) // success
const handleInputConfirm = async (index: number, propertyId: number) => { const handleInputConfirm = async (index: number, propertyId: number) => {
if (inputValue.value) { if (inputValue.value) {
// // 1.
if (attributeList.value[index].values.find((item) => item.name === inputValue.value)) {
message.warning('已存在相同属性值,请重试')
attributeIndex.value = null
inputValue.value = ''
return
}
// 2.1 使
const existValue = attributeOptions.value.find((item) => item.name === inputValue.value)
if (existValue) {
attributeIndex.value = null
inputValue.value = ''
attributeList.value[index].values.push({ id: existValue.id, name: existValue.name })
emit('success', attributeList.value)
return
}
// 2.2
try { try {
const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value }) const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value })
attributeList.value[index].values.push({ id, name: inputValue.value }) attributeList.value[index].values.push({ id, name: inputValue.value })
@ -122,4 +154,9 @@ const handleInputConfirm = async (index: number, propertyId: number) => {
attributeIndex.value = null attributeIndex.value = null
inputValue.value = '' inputValue.value = ''
} }
/** 获取商品属性下拉选项 */
const getAttributeOptions = async (propertyId: number) => {
attributeOptions.value = await PropertyApi.getPropertyValueSimpleList(propertyId)
}
</script> </script>

View File

@ -10,7 +10,22 @@
@keydown.enter.prevent="submitForm" @keydown.enter.prevent="submitForm"
> >
<el-form-item label="属性名称" prop="name"> <el-form-item label="属性名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" /> <el-select
v-model="formData.name"
:reserve-keyword="false"
allow-create
class="!w-360px"
default-first-option
filterable
placeholder="请选择属性名称。如果不存在,可手动输入选择"
>
<el-option
v-for="item in attributeOptions"
:key="item.id"
:label="item.name"
:value="item.name"
/>
</el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
@ -37,6 +52,7 @@ const formRules = reactive({
}) })
const formRef = ref() // Ref const formRef = ref() // Ref
const attributeList = ref([]) // const attributeList = ref([]) //
const attributeOptions = ref([] as PropertyApi.PropertyVO[]) //
const props = defineProps({ const props = defineProps({
propertyList: { propertyList: {
type: Array, type: Array,
@ -60,15 +76,39 @@ watch(
const open = async () => { const open = async () => {
dialogVisible.value = true dialogVisible.value = true
resetForm() resetForm()
//
await getAttributeOptions()
} }
defineExpose({ open }) // open defineExpose({ open }) // open
/** 提交表单 */ /** 提交表单 */
const submitForm = async () => { const submitForm = async () => {
// // 1.1
for (const attrItem of attributeList.value) {
if (attrItem.name === formData.value.name) {
return message.error('该属性已存在,请勿重复添加')
}
}
// 1.2
if (!formRef) return if (!formRef) return
const valid = await formRef.value.validate() const valid = await formRef.value.validate()
if (!valid) return if (!valid) return
// 2.1 使
const existProperty = attributeOptions.value.find((item) => item.name === formData.value.name)
if (existProperty) {
//
attributeList.value.push({
id: existProperty.id,
...formData.value,
values: []
})
//
dialogVisible.value = false
return
}
// 2.2
// //
formLoading.value = true formLoading.value = true
try { try {
@ -80,6 +120,7 @@ const submitForm = async () => {
...formData.value, ...formData.value,
values: [] values: []
}) })
//
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
dialogVisible.value = false dialogVisible.value = false
} finally { } finally {
@ -94,4 +135,14 @@ const resetForm = () => {
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
/** 获取商品属性下拉选项 */
const getAttributeOptions = async () => {
formLoading.value = true
try {
attributeOptions.value = await PropertyApi.getPropertySimpleList()
} finally {
formLoading.value = false
}
}
</script> </script>

View File

@ -1,6 +1,13 @@
<!-- 商品发布 - 库存价格 --> <!-- 商品发布 - 库存价格 -->
<template> <template>
<el-form ref="formRef" :disabled="isDetail" :model="formData" :rules="rules" label-width="120px"> <el-form
ref="formRef"
v-loading="formLoading"
:disabled="isDetail"
:model="formData"
:rules="rules"
label-width="120px"
>
<el-form-item label="分销类型" props="subCommissionType"> <el-form-item label="分销类型" props="subCommissionType">
<el-radio-group <el-radio-group
v-model="formData.subCommissionType" v-model="formData.subCommissionType"
@ -94,7 +101,7 @@ const ruleConfig: RuleConfig[] = [
] ]
const message = useMessage() // const message = useMessage() //
const formLoading = ref(false)
const props = defineProps({ const props = defineProps({
propFormData: { propFormData: {
type: Object as PropType<Spu>, type: Object as PropType<Spu>,

View File

@ -25,7 +25,7 @@ import OrderBrowsingHistory from './OrderBrowsingHistory.vue'
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation' import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
import { isEmpty } from '@/utils/is' import { isEmpty } from '@/utils/is'
import { debounce } from 'lodash-es' import { debounce } from 'lodash-es'
import { ElScrollbar as ElScrollbarType } from 'element-plus/es/components/scrollbar' import { ElScrollbar as ElScrollbarType } from 'element-plus/es/components/scrollbar/index'
defineOptions({ name: 'MemberBrowsingHistory' }) defineOptions({ name: 'MemberBrowsingHistory' })

View File

@ -135,7 +135,7 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="订单金额" prop="refundPrice"> <el-table-column align="center" label="订单金额" min-width="120" prop="refundPrice">
<template #default="scope"> <template #default="scope">
<span>{{ fenToYuan(scope.row.refundPrice) }} </span> <span>{{ fenToYuan(scope.row.refundPrice) }} </span>
</template> </template>
@ -156,7 +156,7 @@
<dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_WAY" :value="scope.row.way" /> <dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_WAY" :value="scope.row.way" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="120"> <el-table-column align="center" fixed="right" label="操作" width="160">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" @click="openAfterSaleDetail(row.id)">处理退款</el-button> <el-button link type="primary" @click="openAfterSaleDetail(row.id)">处理退款</el-button>
</template> </template>
@ -181,9 +181,6 @@ import { cloneDeep } from 'lodash-es'
import { fenToYuan } from '@/utils' import { fenToYuan } from '@/utils'
defineOptions({ name: 'TradeAfterSale' }) defineOptions({ name: 'TradeAfterSale' })
const props = defineProps<{
userId?: number
}>()
const { push } = useRouter() // const { push } = useRouter() //
@ -207,9 +204,9 @@ const queryParams = reactive({
spuName: null, spuName: null,
createTime: [], createTime: [],
way: null, way: null,
type: null, type: null
userId: null
}) })
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
@ -219,27 +216,27 @@ const getList = async () => {
if (data.status === '0') { if (data.status === '0') {
delete data.status delete data.status
} }
if (props.userId) {
data.userId = props.userId
}
// //
const res = await AfterSaleApi.getAfterSalePage(data) const res = await AfterSaleApi.getAfterSalePage(data)
list.value = res.list list.value = res.list as AfterSaleApi.TradeAfterSaleVO[]
total.value = res.total total.value = res.total
} finally { } finally {
loading.value = false loading.value = false
} }
} }
/** 搜索按钮操作 */ /** 搜索按钮操作 */
const handleQuery = async () => { const handleQuery = async () => {
queryParams.pageNo = 1 queryParams.pageNo = 1
await getList() await getList()
} }
/** 重置按钮操作 */ /** 重置按钮操作 */
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value?.resetFields() queryFormRef.value?.resetFields()
handleQuery() handleQuery()
} }
/** tab 切换 */ /** tab 切换 */
const tabClick = async (tab: TabsPaneContext) => { const tabClick = async (tab: TabsPaneContext) => {
queryParams.status = tab.paneName queryParams.status = tab.paneName

View File

@ -2,81 +2,57 @@
<el-descriptions :column="2"> <el-descriptions :column="2">
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<descriptions-item-label label=" 等级 " icon="svg-icon:member_level" /> <descriptions-item-label icon="svg-icon:member_level" label=" 等级 " />
</template> </template>
{{ user.levelName || '无' }} {{ user.levelName || '无' }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<descriptions-item-label label=" 成长值 " icon="ep:suitcase" /> <descriptions-item-label icon="ep:suitcase" label=" 成长值 " />
</template> </template>
{{ user.experience || 0 }} {{ user.experience || 0 }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<descriptions-item-label label=" 当前积分 " icon="ep:coin" /> <descriptions-item-label icon="ep:coin" label=" 当前积分 " />
</template> </template>
{{ user.point || 0 }} {{ user.point || 0 }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<descriptions-item-label label=" 总积分 " icon="ep:coin" /> <descriptions-item-label icon="ep:coin" label=" 总积分 " />
</template> </template>
{{ user.totalPoint || 0 }} {{ user.totalPoint || 0 }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<descriptions-item-label label=" 当前余额 " icon="svg-icon:member_balance" /> <descriptions-item-label icon="svg-icon:member_balance" label=" 当前余额 " />
</template> </template>
{{ fenToYuan(wallet.balance || 0) }} {{ fenToYuan(wallet.balance || 0) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<descriptions-item-label label=" 支出金额 " icon="svg-icon:member_expenditure_balance" /> <descriptions-item-label icon="svg-icon:member_expenditure_balance" label=" 支出金额 " />
</template> </template>
{{ fenToYuan(wallet.totalExpense || 0) }} {{ fenToYuan(wallet.totalExpense || 0) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<descriptions-item-label label=" 充值金额 " icon="svg-icon:member_recharge_balance" /> <descriptions-item-label icon="svg-icon:member_recharge_balance" label=" 充值金额 " />
</template> </template>
{{ fenToYuan(wallet.totalRecharge || 0) }} {{ fenToYuan(wallet.totalRecharge || 0) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import { DescriptionsItemLabel } from '@/components/Descriptions' import { DescriptionsItemLabel } from '@/components/Descriptions'
import * as UserApi from '@/api/member/user' import * as UserApi from '@/api/member/user'
import * as WalletApi from '@/api/pay/wallet/balance' import * as WalletApi from '@/api/pay/wallet/balance'
import { UserTypeEnum } from '@/utils/constants'
import { fenToYuan } from '@/utils' import { fenToYuan } from '@/utils'
const props = defineProps<{ user: UserApi.UserVO }>() // defineProps<{ user: UserApi.UserVO; wallet: WalletApi.WalletVO }>() //
const WALLET_INIT_DATA = {
balance: 0,
totalExpense: 0,
totalRecharge: 0
} as WalletApi.WalletVO //
const wallet = ref<WalletApi.WalletVO>(WALLET_INIT_DATA) //
/** 查询用户钱包信息 */
const getUserWallet = async () => {
if (!props.user.id) {
wallet.value = WALLET_INIT_DATA
return
}
const params = { userId: props.user.id }
wallet.value = (await WalletApi.getWallet(params)) || WALLET_INIT_DATA
}
/** 监听用户编号变化 */
watch(
() => props.user.id,
() => getUserWallet(),
{ immediate: true }
)
</script> </script>
<style scoped lang="scss"> <style lang="scss" scoped>
.cell-item { .cell-item {
display: inline; display: inline;
} }

View File

@ -0,0 +1,276 @@
<template>
<!-- 搜索 -->
<ContentWrap>
<el-form ref="queryFormRef" :inline="true" :model="queryParams" label-width="68px">
<el-form-item label="商品名称" prop="spuName">
<el-input
v-model="queryParams.spuName"
class="!w-280px"
clearable
placeholder="请输入商品 SPU 名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="退款编号" prop="no">
<el-input
v-model="queryParams.no"
class="!w-280px"
clearable
placeholder="请输入退款编号"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="订单编号" prop="orderNo">
<el-input
v-model="queryParams.orderNo"
class="!w-280px"
clearable
placeholder="请输入订单编号"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="售后状态" prop="status">
<el-select
v-model="queryParams.status"
class="!w-280px"
clearable
placeholder="请选择售后状态"
>
<el-option label="全部" value="0" />
<el-option
v-for="dict in getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="售后方式" prop="way">
<el-select
v-model="queryParams.way"
class="!w-280px"
clearable
placeholder="请选择售后方式"
>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_WAY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="售后类型" prop="type">
<el-select
v-model="queryParams.type"
class="!w-280px"
clearable
placeholder="请选择售后类型"
>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-260px"
end-placeholder="自定义时间"
start-placeholder="自定义时间"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-tabs v-model="queryParams.status" @tab-click="tabClick">
<el-tab-pane
v-for="item in statusTabs"
:key="item.label"
:label="item.label"
:name="item.value"
/>
</el-tabs>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="退款编号" min-width="200" prop="no" />
<el-table-column align="center" label="订单编号" min-width="200" prop="orderNo">
<template #default="{ row }">
<el-button link type="primary" @click="openOrderDetail(row.orderId)">
{{ row.orderNo }}
</el-button>
</template>
</el-table-column>
<el-table-column label="商品信息" min-width="600" prop="spuName">
<template #default="{ row }">
<div class="flex items-center">
<el-image
:src="row.picUrl"
class="mr-10px h-30px w-30px"
@click="imagePreview(row.picUrl)"
/>
<span class="mr-10px">{{ row.spuName }}</span>
<el-tag v-for="property in row.properties" :key="property.propertyId" class="mr-10px">
{{ property.propertyName }}: {{ property.valueName }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column align="center" label="订单金额" min-width="120" prop="refundPrice">
<template #default="scope">
<span>{{ fenToYuan(scope.row.refundPrice) }} </span>
</template>
</el-table-column>
<el-table-column align="center" label="申请时间" prop="createTime" width="180">
<template #default="scope">
<span>{{ formatDate(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="售后状态" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column align="center" label="售后方式">
<template #default="scope">
<dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_WAY" :value="scope.row.way" />
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="120">
<template #default="{ row }">
<el-button link type="primary" @click="openAfterSaleDetail(row.id)">处理退款</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
</template>
<script lang="ts" setup>
import * as AfterSaleApi from '@/api/mall/trade/afterSale/index'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import { createImageViewer } from '@/components/ImageViewer'
import { TabsPaneContext } from 'element-plus'
import { cloneDeep } from 'lodash-es'
import { fenToYuan } from '@/utils'
defineOptions({ name: 'UserAfterSaleList' })
const { push } = useRouter() //
const props = defineProps<{
userId: number
}>()
const loading = ref(true) //
const total = ref(0) //
const list = ref<AfterSaleApi.TradeAfterSaleVO[]>([]) //
const statusTabs = ref([
{
label: '全部',
value: '0'
}
])
const queryFormRef = ref() //
//
const queryParams = ref({
pageNo: 1,
pageSize: 10,
no: null,
status: '0',
orderNo: null,
spuName: null,
createTime: [],
way: null,
type: null,
userId: null
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = cloneDeep(queryParams.value)
//
if (data.status === '0') {
delete data.status
}
//
if (props.userId) {
data.userId = props.userId as any
}
const res = await AfterSaleApi.getAfterSalePage(data)
list.value = res.list as AfterSaleApi.TradeAfterSaleVO[]
total.value = res.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = async () => {
queryParams.value.pageNo = 1
await getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
/** tab 切换 */
const tabClick = async (tab: TabsPaneContext) => {
queryParams.value.status = tab.paneName as any
await getList()
}
/** 处理退款 */
const openAfterSaleDetail = (id: number) => {
push({ name: 'TradeAfterSaleDetail', params: { id } })
}
/** 查看订单详情 */
const openOrderDetail = (id: number) => {
push({ name: 'TradeOrderDetail', params: { id } })
}
/** 商品图预览 */
const imagePreview = (imgUrl: string) => {
createImageViewer({
urlList: [imgUrl]
})
}
onMounted(async () => {
await getList()
// statuses
for (const dict of getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_STATUS)) {
statusTabs.value.push({
label: dict.label,
value: dict.value
})
}
})
</script>

View File

@ -0,0 +1,68 @@
<template>
<ContentWrap>
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" label="编号" prop="id" />
<el-table-column align="center" label="钱包编号" prop="walletId" />
<el-table-column align="center" label="关联业务标题" prop="title" />
<el-table-column align="center" label="交易金额" prop="price">
<template #default="{ row }"> {{ fenToYuan(row.price) }} </template>
</el-table-column>
<el-table-column align="center" label="钱包余额" prop="balance">
<template #default="{ row }"> {{ fenToYuan(row.balance) }} </template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="交易时间"
prop="createTime"
width="180px"
/>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as WalletTransactionApi from '@/api/pay/wallet/transaction'
import { fenToYuan } from '@/utils'
defineOptions({ name: 'UserBalanceList' })
const props = defineProps({
walletId: {
type: Number,
required: false
}
})
const loading = ref(true) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
walletId: null
})
const list = ref([]) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
queryParams.walletId = props.walletId as any
const data = await WalletTransactionApi.getWalletTransactionPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -20,7 +20,7 @@
<template #header> <template #header>
<CardTitle title="账户信息" /> <CardTitle title="账户信息" />
</template> </template>
<UserAccountInfo :user="user" /> <UserAccountInfo :user="user" :wallet="wallet" />
</el-card> </el-card>
</el-col> </el-col>
<!-- 下边账户明细 --> <!-- 下边账户明细 -->
@ -40,7 +40,7 @@
<UserExperienceRecordList :user-id="id" /> <UserExperienceRecordList :user-id="id" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="余额" lazy> <el-tab-pane label="余额" lazy>
<WalletTransactionList :user-id="id" /> <UserBalanceList :wallet-id="wallet.id" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="收货地址" lazy> <el-tab-pane label="收货地址" lazy>
<UserAddressList :user-id="id" /> <UserAddressList :user-id="id" />
@ -49,7 +49,7 @@
<UserOrderList :user-id="id" /> <UserOrderList :user-id="id" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="售后管理" lazy> <el-tab-pane label="售后管理" lazy>
<TradeAfterSale :user-id="id" /> <UserAfterSaleList :user-id="id" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="收藏记录" lazy> <el-tab-pane label="收藏记录" lazy>
<UserFavoriteList :user-id="id" /> <UserFavoriteList :user-id="id" />
@ -69,6 +69,7 @@
<UserForm ref="formRef" @success="getUserData(id)" /> <UserForm ref="formRef" @success="getUserData(id)" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as WalletApi from '@/api/pay/wallet/balance'
import * as UserApi from '@/api/member/user' import * as UserApi from '@/api/member/user'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import UserForm from '@/views/member/user/UserForm.vue' import UserForm from '@/views/member/user/UserForm.vue'
@ -82,8 +83,8 @@ import UserOrderList from './UserOrderList.vue'
import UserPointList from './UserPointList.vue' import UserPointList from './UserPointList.vue'
import UserSignList from './UserSignList.vue' import UserSignList from './UserSignList.vue'
import UserFavoriteList from './UserFavoriteList.vue' import UserFavoriteList from './UserFavoriteList.vue'
import WalletTransactionList from '@/views/pay/wallet/transaction/WalletTransactionList.vue' import UserAfterSaleList from './UserAftersaleList.vue'
import TradeAfterSale from '@/views/mall/trade/afterSale/index.vue' import UserBalanceList from './UserBalanceList.vue'
import { CardTitle } from '@/components/Card/index' import { CardTitle } from '@/components/Card/index'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
@ -113,6 +114,24 @@ const { currentRoute } = useRouter() // 路由
const { delView } = useTagsViewStore() // const { delView } = useTagsViewStore() //
const route = useRoute() const route = useRoute()
const id = Number(route.params.id) const id = Number(route.params.id)
/* 用户钱包相关信息 */
const WALLET_INIT_DATA = {
balance: 0,
totalExpense: 0,
totalRecharge: 0
} as WalletApi.WalletVO //
const wallet = ref<WalletApi.WalletVO>(WALLET_INIT_DATA) //
/** 查询用户钱包信息 */
const getUserWallet = async () => {
if (!id) {
wallet.value = WALLET_INIT_DATA
return
}
const params = { userId: id }
wallet.value = (await WalletApi.getWallet(params)) || WALLET_INIT_DATA
}
onMounted(() => { onMounted(() => {
if (!id) { if (!id) {
ElMessage.warning('参数错误,会员编号不能为空!') ElMessage.warning('参数错误,会员编号不能为空!')
@ -120,6 +139,7 @@ onMounted(() => {
return return
} }
getUserData(id) getUserData(id)
getUserWallet()
}) })
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>

View File

@ -10,6 +10,9 @@
<el-form-item label="应用名" prop="name"> <el-form-item label="应用名" prop="name">
<el-input v-model="formData.name" placeholder="请输入应用名" /> <el-input v-model="formData.name" placeholder="请输入应用名" />
</el-form-item> </el-form-item>
<el-form-item label="应用标识" prop="name">
<el-input v-model="formData.appKey" placeholder="请输入应用标识" />
</el-form-item>
<el-form-item label="开启状态" prop="status"> <el-form-item label="开启状态" prop="status">
<el-radio-group v-model="formData.status"> <el-radio-group v-model="formData.status">
<el-radio <el-radio
@ -55,16 +58,15 @@ const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({ const formData = ref({
id: undefined, id: undefined,
name: undefined, name: undefined,
packageId: undefined, appKey: undefined,
contactName: undefined, status: CommonStatusEnum.ENABLE,
contactMobile: undefined, remark: undefined,
accountCount: undefined, orderNotifyUrl: undefined,
expireTime: undefined, refundNotifyUrl: undefined
domain: undefined,
status: CommonStatusEnum.ENABLE
}) })
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '应用名不能为空', trigger: 'blur' }], name: [{ required: true, message: '应用名不能为空', trigger: 'blur' }],
appKey: [{ required: true, message: '应用标识不能为空', trigger: 'blur' }],
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }], status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }],
orderNotifyUrl: [{ required: true, message: '支付结果的回调地址不能为空', trigger: 'blur' }], orderNotifyUrl: [{ required: true, message: '支付结果的回调地址不能为空', trigger: 'blur' }],
refundNotifyUrl: [{ required: true, message: '退款结果的回调地址不能为空', trigger: 'blur' }] refundNotifyUrl: [{ required: true, message: '退款结果的回调地址不能为空', trigger: 'blur' }]
@ -123,7 +125,8 @@ const resetForm = () => {
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
remark: undefined, remark: undefined,
orderNotifyUrl: undefined, orderNotifyUrl: undefined,
refundNotifyUrl: undefined refundNotifyUrl: undefined,
appKey: undefined
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }

View File

@ -1,22 +1,22 @@
<template> <template>
<div> <div>
<Dialog v-model="dialogVisible" :title="dialogTitle" @closed="close" width="830px"> <Dialog v-model="dialogVisible" :title="dialogTitle" width="830px" @closed="close">
<el-form <el-form
ref="formRef" ref="formRef"
:model="formData"
:formRules="formRules"
label-width="100px"
v-loading="formLoading" v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="100px"
> >
<el-form-item label-width="180px" label="渠道费率" prop="feeRate"> <el-form-item label="渠道费率" label-width="180px" prop="feeRate">
<el-input v-model="formData.feeRate" placeholder="请输入渠道费率" clearable> <el-input v-model="formData.feeRate" clearable placeholder="请输入渠道费率">
<template #append>%</template> <template #append>%</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="开放平台 APPID" prop="config.appId"> <el-form-item label="开放平台 APPID" label-width="180px" prop="config.appId">
<el-input v-model="formData.config.appId" placeholder="请输入开放平台 APPID" clearable /> <el-input v-model="formData.config.appId" clearable placeholder="请输入开放平台 APPID" />
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="渠道状态" prop="status"> <el-form-item label="渠道状态" label-width="180px" prop="status">
<el-radio-group v-model="formData.status"> <el-radio-group v-model="formData.status">
<el-radio <el-radio
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
@ -27,7 +27,7 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="网关地址" prop="config.serverUrl"> <el-form-item label="网关地址" label-width="180px" prop="config.serverUrl">
<el-radio-group v-model="formData.config.serverUrl"> <el-radio-group v-model="formData.config.serverUrl">
<el-radio label="https://openapi.alipay.com/gateway.do">线上环境</el-radio> <el-radio label="https://openapi.alipay.com/gateway.do">线上环境</el-radio>
<el-radio label="https://openapi-sandbox.dl.alipaydev.com/gateway.do"> <el-radio label="https://openapi-sandbox.dl.alipaydev.com/gateway.do">
@ -35,128 +35,148 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="算法类型" prop="config.signType"> <el-form-item label="算法类型" label-width="180px" prop="config.signType">
<el-radio-group v-model="formData.config.signType"> <el-radio-group v-model="formData.config.signType">
<el-radio key="RSA2" label="RSA2">RSA2</el-radio> <el-radio key="RSA2" label="RSA2">RSA2</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="公钥类型" prop="config.mode"> <el-form-item label="公钥类型" label-width="180px" prop="config.mode">
<el-radio-group v-model="formData.config.mode"> <el-radio-group v-model="formData.config.mode">
<el-radio key="公钥模式" :label="1">公钥模式</el-radio> <el-radio key="公钥模式" :label="1">公钥模式</el-radio>
<el-radio key="证书模式" :label="2">证书模式</el-radio> <el-radio key="证书模式" :label="2">证书模式</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<div v-if="formData.config.mode === 1"> <div v-if="formData.config.mode === 1">
<el-form-item label-width="180px" label="应用私钥" prop="config.privateKey"> <el-form-item label="应用私钥" label-width="180px" prop="config.privateKey">
<el-input <el-input
type="textarea"
:autosize="{ minRows: 8, maxRows: 8 }"
v-model="formData.config.privateKey" v-model="formData.config.privateKey"
placeholder="请输入应用私钥" :autosize="{ minRows: 8, maxRows: 8 }"
clearable
:style="{ width: '100%' }" :style="{ width: '100%' }"
clearable
placeholder="请输入应用私钥"
type="textarea"
/> />
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="支付宝公钥" prop="config.alipayPublicKey"> <el-form-item label="支付宝公钥" label-width="180px" prop="config.alipayPublicKey">
<el-input <el-input
type="textarea"
:autosize="{ minRows: 8, maxRows: 8 }"
v-model="formData.config.alipayPublicKey" v-model="formData.config.alipayPublicKey"
placeholder="请输入支付宝公钥" :autosize="{ minRows: 8, maxRows: 8 }"
clearable
:style="{ width: '100%' }" :style="{ width: '100%' }"
clearable
placeholder="请输入支付宝公钥"
type="textarea"
/> />
</el-form-item> </el-form-item>
</div> </div>
<div v-if="formData.config.mode === 2"> <div v-if="formData.config.mode === 2">
<el-form-item label-width="180px" label="应用私钥" prop="config.privateKey"> <el-form-item label="应用私钥" label-width="180px" prop="config.privateKey">
<el-input <el-input
type="textarea"
:autosize="{ minRows: 8, maxRows: 8 }"
v-model="formData.config.privateKey" v-model="formData.config.privateKey"
placeholder="请输入应用私钥" :autosize="{ minRows: 8, maxRows: 8 }"
clearable
:style="{ width: '100%' }" :style="{ width: '100%' }"
clearable
placeholder="请输入应用私钥"
type="textarea"
/> />
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="商户公钥应用证书" prop="config.appCertContent"> <el-form-item label="商户公钥应用证书" label-width="180px" prop="config.appCertContent">
<el-input <el-input
v-model="formData.config.appCertContent" v-model="formData.config.appCertContent"
type="textarea"
placeholder="请上传商户公钥应用证书"
readonly
:autosize="{ minRows: 8, maxRows: 8 }" :autosize="{ minRows: 8, maxRows: 8 }"
:style="{ width: '100%' }" :style="{ width: '100%' }"
placeholder="请上传商户公钥应用证书"
readonly
type="textarea"
/> />
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label=""> <el-form-item label="" label-width="180px">
<el-upload <el-upload
action=""
ref="privateKeyContentFile" ref="privateKeyContentFile"
:limit="1"
:accept="fileAccept" :accept="fileAccept"
:http-request="appCertUpload"
:before-upload="fileBeforeUpload" :before-upload="fileBeforeUpload"
:http-request="appCertUpload"
:limit="1"
action=""
> >
<el-button type="primary"> <el-button type="primary">
<Icon icon="ep:upload" class="mr-5px" /> 点击上传 <Icon class="mr-5px" icon="ep:upload" />
点击上传
</el-button> </el-button>
</el-upload> </el-upload>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label-width="180px"
label="支付宝公钥证书" label="支付宝公钥证书"
label-width="180px"
prop="config.alipayPublicCertContent" prop="config.alipayPublicCertContent"
> >
<el-input <el-input
v-model="formData.config.alipayPublicCertContent" v-model="formData.config.alipayPublicCertContent"
type="textarea"
placeholder="请上传支付宝公钥证书"
readonly
:autosize="{ minRows: 8, maxRows: 8 }" :autosize="{ minRows: 8, maxRows: 8 }"
:style="{ width: '100%' }" :style="{ width: '100%' }"
placeholder="请上传支付宝公钥证书"
readonly
type="textarea"
/> />
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label=""> <el-form-item label="" label-width="180px">
<el-upload <el-upload
ref="privateCertContentFile" ref="privateCertContentFile"
action=""
:limit="1"
:accept="fileAccept" :accept="fileAccept"
:before-upload="fileBeforeUpload" :before-upload="fileBeforeUpload"
:http-request="alipayPublicCertUpload" :http-request="alipayPublicCertUpload"
:limit="1"
action=""
> >
<el-button type="primary"> <el-button type="primary">
<Icon icon="ep:upload" class="mr-5px" /> 点击上传 <Icon class="mr-5px" icon="ep:upload" />
点击上传
</el-button> </el-button>
</el-upload> </el-upload>
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="根证书" prop="config.rootCertContent"> <el-form-item label="根证书" label-width="180px" prop="config.rootCertContent">
<el-input <el-input
v-model="formData.config.rootCertContent" v-model="formData.config.rootCertContent"
type="textarea"
placeholder="请上传根证书"
readonly
:autosize="{ minRows: 8, maxRows: 8 }" :autosize="{ minRows: 8, maxRows: 8 }"
:style="{ width: '100%' }" :style="{ width: '100%' }"
placeholder="请上传根证书"
readonly
type="textarea"
/> />
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label=""> <el-form-item label="" label-width="180px">
<el-upload <el-upload
ref="privateCertContentFile" ref="privateCertContentFile"
:limit="1"
:accept="fileAccept" :accept="fileAccept"
action=""
:before-upload="fileBeforeUpload" :before-upload="fileBeforeUpload"
:http-request="rootCertUpload" :http-request="rootCertUpload"
:limit="1"
action=""
> >
<el-button type="primary"> <el-button type="primary">
<Icon icon="ep:upload" class="mr-5px" /> 点击上传 <Icon class="mr-5px" icon="ep:upload" />
点击上传
</el-button> </el-button>
</el-upload> </el-upload>
</el-form-item> </el-form-item>
</div> </div>
<el-form-item label-width="180px" label="备注" prop="remark">
<el-form-item label="接口内容加密方式" label-width="180px" prop="config.encryptType">
<el-radio-group v-model="formData.config.encryptType">
<el-radio key="NONE" label="">无加密</el-radio>
<el-radio key="AES" label="AES">AES</el-radio>
</el-radio-group>
</el-form-item>
<div v-if="formData.config.encryptType === 'AES'">
<el-form-item label="接口内容加密密钥" label-width="180px" prop="config.encryptKey">
<el-input
v-model="formData.config.encryptKey"
clearable
placeholder="请输入接口内容加密密钥"
/>
</el-form-item>
</div>
<el-form-item label="备注" label-width="180px" prop="remark">
<el-input v-model="formData.remark" :style="{ width: '100%' }" /> <el-input v-model="formData.remark" :style="{ width: '100%' }" />
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -195,7 +215,9 @@ const formData = ref<any>({
alipayPublicKey: '', alipayPublicKey: '',
appCertContent: '', appCertContent: '',
alipayPublicCertContent: '', alipayPublicCertContent: '',
rootCertContent: '' rootCertContent: '',
encryptType: '',
encryptKey: ''
} }
}) })
const formRules = { const formRules = {
@ -213,7 +235,8 @@ const formRules = {
'config.alipayPublicCertContent': [ 'config.alipayPublicCertContent': [
{ required: true, message: '请上传支付宝公钥证书', trigger: 'blur' } { required: true, message: '请上传支付宝公钥证书', trigger: 'blur' }
], ],
'config.rootCertContent': [{ required: true, message: '请上传指定根证书', trigger: 'blur' }] 'config.rootCertContent': [{ required: true, message: '请上传指定根证书', trigger: 'blur' }],
'config.encryptKey': [{ required: true, message: '请输入接口内容加密密钥', trigger: 'blur' }]
} }
const fileAccept = '.crt' const fileAccept = '.crt'
const formRef = ref() // Ref const formRef = ref() // Ref
@ -281,7 +304,9 @@ const resetForm = (appId, code) => {
alipayPublicKey: '', alipayPublicKey: '',
appCertContent: '', appCertContent: '',
alipayPublicCertContent: '', alipayPublicCertContent: '',
rootCertContent: '' rootCertContent: '',
encryptType: '',
encryptKey: ''
} }
} }
formRef.value?.resetFields() formRef.value?.resetFields()

View File

@ -1,14 +1,14 @@
<template> <template>
<div> <div>
<Dialog v-model="dialogVisible" :title="dialogTitle" @closed="close" width="800px"> <Dialog v-model="dialogVisible" :title="dialogTitle" width="800px">
<el-form <el-form
ref="formRef" ref="formRef"
v-loading="formLoading"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="100px" label-width="100px"
v-loading="formLoading"
> >
<el-form-item label-width="180px" label="渠道状态" prop="status"> <el-form-item label="渠道状态" label-width="180px" prop="status">
<el-radio-group v-model="formData.status"> <el-radio-group v-model="formData.status">
<el-radio <el-radio
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
@ -19,7 +19,7 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="备注" prop="remark"> <el-form-item label="备注" label-width="180px" prop="remark">
<el-input v-model="formData.remark" :style="{ width: '100%' }" /> <el-input v-model="formData.remark" :style="{ width: '100%' }" />
</el-form-item> </el-form-item>
</el-form> </el-form>

View File

@ -1,14 +1,14 @@
<template> <template>
<div> <div>
<Dialog v-model="dialogVisible" :title="dialogTitle" @closed="close" width="800px"> <Dialog v-model="dialogVisible" :title="dialogTitle" width="800px">
<el-form <el-form
ref="formRef" ref="formRef"
v-loading="formLoading"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="100px" label-width="100px"
v-loading="formLoading"
> >
<el-form-item label-width="180px" label="渠道状态" prop="status"> <el-form-item label="渠道状态" label-width="180px" prop="status">
<el-radio-group v-model="formData.status"> <el-radio-group v-model="formData.status">
<el-radio <el-radio
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
@ -19,7 +19,7 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="备注" prop="remark"> <el-form-item label="备注" label-width="180px" prop="remark">
<el-input v-model="formData.remark" :style="{ width: '100%' }" /> <el-input v-model="formData.remark" :style="{ width: '100%' }" />
</el-form-item> </el-form-item>
</el-form> </el-form>

View File

@ -1,35 +1,35 @@
<template> <template>
<div> <div>
<Dialog v-model="dialogVisible" :title="dialogTitle" @close="close" width="800px"> <Dialog v-model="dialogVisible" :title="dialogTitle" width="800px">
<el-form <el-form
ref="formRef" ref="formRef"
v-loading="formLoading"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="120px" label-width="120px"
v-loading="formLoading"
> >
<el-form-item label-width="180px" label="渠道费率" prop="feeRate"> <el-form-item label="渠道费率" label-width="180px" prop="feeRate">
<el-input <el-input
v-model="formData.feeRate" v-model="formData.feeRate"
placeholder="请输入渠道费率"
clearable
:style="{ width: '100%' }" :style="{ width: '100%' }"
clearable
placeholder="请输入渠道费率"
> >
<template #append>%</template> <template #append>%</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="微信 APPID" prop="config.appId"> <el-form-item label="微信 APPID" label-width="180px" prop="config.appId">
<el-input <el-input
v-model="formData.config.appId" v-model="formData.config.appId"
placeholder="请输入微信 APPID"
clearable
:style="{ width: '100%' }" :style="{ width: '100%' }"
clearable
placeholder="请输入微信 APPID"
/> />
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="商户号" prop="config.mchId"> <el-form-item label="商户号" label-width="180px" prop="config.mchId">
<el-input v-model="formData.config.mchId" :style="{ width: '100%' }" /> <el-input v-model="formData.config.mchId" :style="{ width: '100%' }" />
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="渠道状态" prop="status"> <el-form-item label="渠道状态" label-width="180px" prop="status">
<el-radio-group v-model="formData.status"> <el-radio-group v-model="formData.status">
<el-radio <el-radio
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
@ -40,95 +40,91 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="API 版本" prop="config.apiVersion"> <el-form-item label="API 版本" label-width="180px" prop="config.apiVersion">
<el-radio-group v-model="formData.config.apiVersion"> <el-radio-group v-model="formData.config.apiVersion">
<el-radio label="v2">v2</el-radio> <el-radio label="v2">v2</el-radio>
<el-radio label="v3">v3</el-radio> <el-radio label="v3">v3</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<div v-if="formData.config.apiVersion === 'v2'"> <div v-if="formData.config.apiVersion === 'v2'">
<el-form-item label-width="180px" label="商户密钥" prop="config.mchKey"> <el-form-item label="商户密钥" label-width="180px" prop="config.mchKey">
<el-input <el-input v-model="formData.config.mchKey" clearable placeholder="请输入商户密钥" />
v-model="formData.config.mchKey"
placeholder="请输入商户密钥"
clearable
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label-width="180px"
label="apiclient_cert.p12 证书" label="apiclient_cert.p12 证书"
label-width="180px"
prop="config.keyContent" prop="config.keyContent"
> >
<el-input <el-input
v-model="formData.config.keyContent" v-model="formData.config.keyContent"
type="textarea"
placeholder="请上传 apiclient_cert.p12 证书"
readonly
:autosize="{ minRows: 8, maxRows: 8 }" :autosize="{ minRows: 8, maxRows: 8 }"
:style="{ width: '100%' }" :style="{ width: '100%' }"
placeholder="请上传 apiclient_cert.p12 证书"
readonly
type="textarea"
/> />
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label=""> <el-form-item label="" label-width="180px">
<el-upload <el-upload
:before-upload="p12FileBeforeUpload"
:http-request="keyContentUpload"
:limit="1" :limit="1"
accept=".p12" accept=".p12"
action="" action=""
:before-upload="p12FileBeforeUpload"
:http-request="keyContentUpload"
> >
<el-button type="primary"> <el-button type="primary">
<Icon icon="ep:upload" class="mr-5px" /> <Icon class="mr-5px" icon="ep:upload" />
点击上传 点击上传
</el-button> </el-button>
</el-upload> </el-upload>
</el-form-item> </el-form-item>
</div> </div>
<div v-if="formData.config.apiVersion === 'v3'"> <div v-if="formData.config.apiVersion === 'v3'">
<el-form-item label-width="180px" label="API V3 密钥" prop="config.apiV3Key"> <el-form-item label="API V3 密钥" label-width="180px" prop="config.apiV3Key">
<el-input <el-input
v-model="formData.config.apiV3Key" v-model="formData.config.apiV3Key"
placeholder="请输入 API V3 密钥"
clearable clearable
placeholder="请输入 API V3 密钥"
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label-width="180px"
label="apiclient_key.pem 证书" label="apiclient_key.pem 证书"
label-width="180px"
prop="config.privateKeyContent" prop="config.privateKeyContent"
> >
<el-input <el-input
v-model="formData.config.privateKeyContent" v-model="formData.config.privateKeyContent"
type="textarea"
placeholder="请上传 apiclient_key.pem 证书"
readonly
:autosize="{ minRows: 8, maxRows: 8 }" :autosize="{ minRows: 8, maxRows: 8 }"
:style="{ width: '100%' }" :style="{ width: '100%' }"
placeholder="请上传 apiclient_key.pem 证书"
readonly
type="textarea"
/> />
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="" prop="privateKeyContentFile"> <el-form-item label="" label-width="180px" prop="privateKeyContentFile">
<el-upload <el-upload
ref="privateKeyContentFile" ref="privateKeyContentFile"
:before-upload="pemFileBeforeUpload"
:http-request="privateKeyContentUpload"
:limit="1" :limit="1"
accept=".pem" accept=".pem"
action="" action=""
:before-upload="pemFileBeforeUpload"
:http-request="privateKeyContentUpload"
> >
<el-button type="primary"> <el-button type="primary">
<Icon icon="ep:upload" class="mr-5px" /> <Icon class="mr-5px" icon="ep:upload" />
点击上传 点击上传
</el-button> </el-button>
</el-upload> </el-upload>
</el-form-item> </el-form-item>
<el-form-item label-width="180px" label="证书序列号" prop="config.certSerialNo"> <el-form-item label="证书序列号" label-width="180px" prop="config.certSerialNo">
<el-input <el-input
v-model="formData.config.certSerialNo" v-model="formData.config.certSerialNo"
placeholder="请输入证书序列号"
clearable clearable
placeholder="请输入证书序列号"
/> />
</el-form-item> </el-form-item>
</div> </div>
<el-form-item label-width="180px" label="备注" prop="remark"> <el-form-item label="备注" label-width="180px" prop="remark">
<el-input v-model="formData.remark" :style="{ width: '100%' }" /> <el-input v-model="formData.remark" :style="{ width: '100%' }" />
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -182,9 +178,7 @@ const formRules = {
'config.privateKeyContent': [ 'config.privateKeyContent': [
{ required: true, message: '请上传 apiclient_key.pem 证书', trigger: 'blur' } { required: true, message: '请上传 apiclient_key.pem 证书', trigger: 'blur' }
], ],
'config.certSerialNo': [ 'config.certSerialNo': [{ required: true, message: '请输入证书序列号', trigger: 'blur' }],
{ required: true, message: '请输入证书序列号', trigger: 'blur' }
],
'config.apiV3Key': [{ required: true, message: '请上传 api V3 密钥值', trigger: 'blur' }] 'config.apiV3Key': [{ required: true, message: '请上传 api V3 密钥值', trigger: 'blur' }]
} }
const formRef = ref() // Ref const formRef = ref() // Ref

View File

@ -3,27 +3,27 @@
<!-- 搜索 --> <!-- 搜索 -->
<ContentWrap> <ContentWrap>
<el-form <el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef" ref="queryFormRef"
:inline="true" :inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px" label-width="68px"
> >
<el-form-item label="应用名" prop="name"> <el-form-item label="应用名" prop="name">
<el-input <el-input
v-model="queryParams.name" v-model="queryParams.name"
placeholder="请输入应用名"
clearable
@keyup.enter="handleQuery"
class="!w-240px" class="!w-240px"
clearable
placeholder="请输入应用名"
@keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="开启状态" prop="status"> <el-form-item label="开启状态" prop="status">
<el-select <el-select
v-model="queryParams.status" v-model="queryParams.status"
placeholder="请选择开启状态"
clearable
class="!w-240px" class="!w-240px"
clearable
placeholder="请选择开启状态"
> >
<el-option <el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
@ -36,25 +36,25 @@
<el-form-item label="创建时间" prop="createTime"> <el-form-item label="创建时间" prop="createTime">
<el-date-picker <el-date-picker
v-model="queryParams.createTime" v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px" class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"> <el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> <Icon class="mr-5px" icon="ep:search" />
搜索 搜索
</el-button> </el-button>
<el-button @click="resetQuery"> <el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> <Icon class="mr-5px" icon="ep:refresh" />
重置 重置
</el-button> </el-button>
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['pay:app:create']"> <el-button v-hasPermi="['pay:app:create']" plain type="primary" @click="openForm('create')">
<Icon icon="ep:plus" class="mr-5px" /> <Icon class="mr-5px" icon="ep:plus" />
新增 新增
</el-button> </el-button>
</el-form-item> </el-form-item>
@ -64,9 +64,9 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="应用编号" align="center" prop="id" /> <el-table-column align="center" label="应用标识" prop="appKey" />
<el-table-column label="应用名" align="center" prop="name" /> <el-table-column align="center" label="应用名" min-width="90" prop="name" />
<el-table-column label="开启状态" align="center" prop="status"> <el-table-column align="center" label="开启状态" prop="status">
<template #default="scope"> <template #default="scope">
<el-switch <el-switch
v-model="scope.row.status" v-model="scope.row.status"
@ -76,26 +76,28 @@
/> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="支付宝配置" align="center"> <el-table-column align="center" label="支付宝配置">
<el-table-column <el-table-column
:label="channel.name"
align="center"
v-for="channel in alipayChannels" v-for="channel in alipayChannels"
:key="channel.code" :key="channel.code"
:label="channel.name.replace('支付宝', '')"
align="center"
> >
<template #default="scope"> <template #default="scope">
<el-button <el-button
type="success"
v-if="isChannelExists(scope.row.channelCodes, channel.code)" v-if="isChannelExists(scope.row.channelCodes, channel.code)"
@click="openChannelForm(scope.row, channel.code)"
circle circle
size="small"
type="success"
@click="openChannelForm(scope.row, channel.code)"
> >
<Icon icon="ep:check" /> <Icon icon="ep:check" />
</el-button> </el-button>
<el-button <el-button
v-else v-else
type="danger"
circle circle
size="small"
type="danger"
@click="openChannelForm(scope.row, channel.code)" @click="openChannelForm(scope.row, channel.code)"
> >
<Icon icon="ep:close" /> <Icon icon="ep:close" />
@ -103,26 +105,28 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="微信配置" align="center"> <el-table-column align="center" label="微信配置">
<el-table-column <el-table-column
:label="channel.name"
align="center"
v-for="channel in wxChannels" v-for="channel in wxChannels"
:key="channel.code" :key="channel.code"
:label="channel.name.replace('微信', '')"
align="center"
> >
<template #default="scope"> <template #default="scope">
<el-button <el-button
type="success"
v-if="isChannelExists(scope.row.channelCodes, channel.code)" v-if="isChannelExists(scope.row.channelCodes, channel.code)"
@click="openChannelForm(scope.row, channel.code)"
circle circle
size="small"
type="success"
@click="openChannelForm(scope.row, channel.code)"
> >
<Icon icon="ep:check" /> <Icon icon="ep:check" />
</el-button> </el-button>
<el-button <el-button
v-else v-else
type="danger"
circle circle
size="small"
type="danger"
@click="openChannelForm(scope.row, channel.code)" @click="openChannelForm(scope.row, channel.code)"
> >
<Icon icon="ep:close" /> <Icon icon="ep:close" />
@ -130,21 +134,23 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="钱包支付配置" align="center"> <el-table-column align="center" label="钱包支付配置">
<el-table-column :label="PayChannelEnum.WALLET.name" align="center"> <el-table-column :label="PayChannelEnum.WALLET.name" align="center">
<template #default="scope"> <template #default="scope">
<el-button <el-button
type="success"
circle
v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.WALLET.code)" v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.WALLET.code)"
circle
size="small"
type="success"
@click="openChannelForm(scope.row, PayChannelEnum.WALLET.code)" @click="openChannelForm(scope.row, PayChannelEnum.WALLET.code)"
> >
<Icon icon="ep:check" /> <Icon icon="ep:check" />
</el-button> </el-button>
<el-button <el-button
v-else v-else
type="danger"
circle circle
size="small"
type="danger"
@click="openChannelForm(scope.row, PayChannelEnum.WALLET.code)" @click="openChannelForm(scope.row, PayChannelEnum.WALLET.code)"
> >
<Icon icon="ep:close" /> <Icon icon="ep:close" />
@ -152,21 +158,23 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="模拟支付配置" align="center"> <el-table-column align="center" label="模拟支付配置">
<el-table-column :label="PayChannelEnum.MOCK.name" align="center"> <el-table-column :label="PayChannelEnum.MOCK.name" align="center">
<template #default="scope"> <template #default="scope">
<el-button <el-button
type="success"
circle
v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.MOCK.code)" v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.MOCK.code)"
circle
size="small"
type="success"
@click="openChannelForm(scope.row, PayChannelEnum.MOCK.code)" @click="openChannelForm(scope.row, PayChannelEnum.MOCK.code)"
> >
<Icon icon="ep:check" /> <Icon icon="ep:check" />
</el-button> </el-button>
<el-button <el-button
v-else v-else
type="danger"
circle circle
size="small"
type="danger"
@click="openChannelForm(scope.row, PayChannelEnum.MOCK.code)" @click="openChannelForm(scope.row, PayChannelEnum.MOCK.code)"
> >
<Icon icon="ep:close" /> <Icon icon="ep:close" />
@ -174,21 +182,21 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" min-width="110" fixed="right"> <el-table-column align="center" fixed="right" label="操作" min-width="110">
<template #default="scope"> <template #default="scope">
<el-button <el-button
v-hasPermi="['pay:app:update']"
link link
type="primary" type="primary"
@click="openForm('update', scope.row.id)" @click="openForm('update', scope.row.id)"
v-hasPermi="['pay:app:update']"
> >
编辑 编辑
</el-button> </el-button>
<el-button <el-button
v-hasPermi="['pay:app:delete']"
link link
type="danger" type="danger"
@click="handleDelete(scope.row.id)" @click="handleDelete(scope.row.id)"
v-hasPermi="['pay:app:delete']"
> >
删除 删除
</el-button> </el-button>
@ -197,9 +205,9 @@
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
<Pagination <Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize" v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList" @pagination="getList"
/> />
</ContentWrap> </ContentWrap>
@ -255,7 +263,7 @@ const wxChannels = [
PayChannelEnum.WX_APP, PayChannelEnum.WX_APP,
PayChannelEnum.WX_NATIVE, PayChannelEnum.WX_NATIVE,
PayChannelEnum.WX_WAP, PayChannelEnum.WX_WAP,
PayChannelEnum.WX_BAR, PayChannelEnum.WX_BAR
] ]
/** 查询列表 */ /** 查询列表 */

View File

@ -13,19 +13,19 @@ module.exports = {
'at-rule-no-unknown': [ 'at-rule-no-unknown': [
true, true,
{ {
ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin'] ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin', 'extend']
} }
], ],
'media-query-no-invalid': null, 'media-query-no-invalid': null,
'function-no-unknown': null, 'function-no-unknown': null,
'no-empty-source': null, 'no-empty-source': null,
'named-grid-areas-no-invalid': null, 'named-grid-areas-no-invalid': null,
'unicode-bom': 'never', // 'unicode-bom': 'never',
'no-descending-specificity': null, 'no-descending-specificity': null,
'font-family-no-missing-generic-family-keyword': null, 'font-family-no-missing-generic-family-keyword': null,
'declaration-colon-space-after': 'always-single-line', // 'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never', // 'declaration-colon-space-before': 'never',
'declaration-block-trailing-semicolon': null, // 'declaration-block-trailing-semicolon': null,
'rule-empty-line-before': [ 'rule-empty-line-before': [
'always', 'always',
{ {

View File

@ -24,11 +24,11 @@
"@/*": ["src/*"] "@/*": ["src/*"]
}, },
"types": [ "types": [
"@intlify/unplugin-vue-i18n/types", // "@intlify/unplugin-vue-i18n/types",
"vite/client", "vite/client"
"element-plus/global", // "element-plus/global",
"@types/qrcode", // "@types/qrcode",
"vite-plugin-svg-icons/client" // "vite-plugin-svg-icons/client"
], ],
"outDir": "target", // tsconfig.json "outDir": "target", // tsconfig.json
"typeRoots": ["./node_modules/@types/", "./types"] "typeRoots": ["./node_modules/@types/", "./types"]

View File

@ -1,78 +1,85 @@
import { resolve } from 'path' import {resolve} from 'path'
import { loadEnv } from 'vite' import type {ConfigEnv, UserConfig} from 'vite'
import type { UserConfig, ConfigEnv } from 'vite' import {loadEnv} from 'vite'
import { createVitePlugins } from './build/vite' import {createVitePlugins} from './build/vite'
import { include, exclude } from "./build/vite/optimize" import {exclude, include} from "./build/vite/optimize"
// 当前执行node命令时文件夹的地址(工作目录) // 当前执行node命令时文件夹的地址(工作目录)
const root = process.cwd() const root = process.cwd()
// 路径查找 // 路径查找
function pathResolve(dir: string) { function pathResolve(dir: string) {
return resolve(root, '.', dir) return resolve(root, '.', dir)
} }
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default ({ command, mode }: ConfigEnv): UserConfig => { export default ({command, mode}: ConfigEnv): UserConfig => {
let env = {} as any let env = {} as any
const isBuild = command === 'build' const isBuild = command === 'build'
if (!isBuild) { if (!isBuild) {
env = loadEnv((process.argv[3] === '--mode' ? process.argv[4] : process.argv[3]), root) env = loadEnv((process.argv[3] === '--mode' ? process.argv[4] : process.argv[3]), root)
} else { } else {
env = loadEnv(mode, root) env = loadEnv(mode, root)
} }
return { return {
base: env.VITE_BASE_PATH, base: env.VITE_BASE_PATH,
root: root, root: root,
// 服务端渲染 // 服务端渲染
server: { server: {
port: env.VITE_PORT, // 端口号 port: env.VITE_PORT, // 端口号
host: "0.0.0.0", host: "0.0.0.0",
open: env.VITE_OPEN === 'true', open: env.VITE_OPEN === 'true',
// 本地跨域代理. 目前注释的原因暂时没有用途server 端已经支持跨域 // 本地跨域代理. 目前注释的原因暂时没有用途server 端已经支持跨域
// proxy: { // proxy: {
// ['/admin-api']: { // ['/admin-api']: {
// target: env.VITE_BASE_URL, // target: env.VITE_BASE_URL,
// ws: false, // ws: false,
// changeOrigin: true, // changeOrigin: true,
// rewrite: (path) => path.replace(new RegExp(`^/admin-api`), ''), // rewrite: (path) => path.replace(new RegExp(`^/admin-api`), ''),
// }, // },
// }, // },
},
// 项目使用的vite插件。 单独提取到build/vite/plugin中管理
plugins: createVitePlugins(),
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "./src/styles/variables.scss";',
javascriptEnabled: true
}
}
},
resolve: {
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.scss', '.css'],
alias: [
{
find: 'vue-i18n',
replacement: 'vue-i18n/dist/vue-i18n.cjs.js'
}, },
{ // 项目使用的vite插件。 单独提取到build/vite/plugin中管理
find: /\@\//, plugins: createVitePlugins(),
replacement: `${pathResolve('src')}/` css: {
} preprocessorOptions: {
] scss: {
}, additionalData: '@import "./src/styles/variables.scss";',
build: { javascriptEnabled: true
minify: 'terser', }
outDir: env.VITE_OUT_DIR || 'dist', }
sourcemap: env.VITE_SOURCEMAP === 'true' ? 'inline' : false, },
// brotliSize: false, resolve: {
terserOptions: { extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.scss', '.css'],
compress: { alias: [
drop_debugger: env.VITE_DROP_DEBUGGER === 'true', {
drop_console: env.VITE_DROP_CONSOLE === 'true' find: 'vue-i18n',
} replacement: 'vue-i18n/dist/vue-i18n.cjs.js'
} },
}, {
optimizeDeps: { include, exclude } find: /\@\//,
} replacement: `${pathResolve('src')}/`
}
]
},
build: {
minify: 'terser',
outDir: env.VITE_OUT_DIR || 'dist',
sourcemap: env.VITE_SOURCEMAP === 'true' ? 'inline' : false,
// brotliSize: false,
terserOptions: {
compress: {
drop_debugger: env.VITE_DROP_DEBUGGER === 'true',
drop_console: env.VITE_DROP_CONSOLE === 'true'
}
},
rollupOptions: {
output: {
manualChunks: {
echarts: ['echarts'] // 将 echarts 单独打包,参考 https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues/IAB1SX 讨论
}
},
},
},
optimizeDeps: {include, exclude}
}
} }