Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm
# Conflicts: # src/views/bpm/model/ModelForm.vue # src/views/bpm/model/index.vuepull/452/head
commit
fb19dd2476
|
@ -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"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
2560
pnpm-lock.yaml
2560
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -54,8 +54,4 @@ body {
|
||||||
.#{$prefix-cls}-grey-mode {
|
.#{$prefix-cls}-grey-mode {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar__view {
|
|
||||||
height: 99%!important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
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'
|
import request from '@/config/axios' // AI 思维导图 VO
|
||||||
|
|
||||||
// AI 思维导图 VO
|
// AI 思维导图 VO
|
||||||
export interface MindMapVO {
|
export interface MindMapVO {
|
||||||
|
|
|
@ -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 } })
|
||||||
|
}
|
||||||
|
|
|
@ -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 // 成本价
|
||||||
|
|
|
@ -16,6 +16,7 @@ export interface CombinationActivityVO {
|
||||||
virtualGroup?: number
|
virtualGroup?: number
|
||||||
status?: number
|
status?: number
|
||||||
limitDuration?: number
|
limitDuration?: number
|
||||||
|
combinationPrice?: number
|
||||||
products: CombinationProductVO[]
|
products: CombinationProductVO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ export interface SpuExtension extends Spu {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询拼团活动列表
|
// 查询拼团活动列表
|
||||||
export const getCombinationActivityPage = async (params) => {
|
export const getCombinationActivityPage = async (params: any) => {
|
||||||
return await request.get({ url: '/promotion/combination-activity/page', params })
|
return await request.get({ url: '/promotion/combination-activity/page', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +46,11 @@ export const getCombinationActivity = async (id: number) => {
|
||||||
return await request.get({ url: '/promotion/combination-activity/get?id=' + id })
|
return await request.get({ url: '/promotion/combination-activity/get?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获得拼团活动列表,基于活动编号数组
|
||||||
|
export const getCombinationActivityListByIds = (ids: number[]) => {
|
||||||
|
return request.get({ url: `/promotion/combination-activity/list-by-ids?ids=${ids}` })
|
||||||
|
}
|
||||||
|
|
||||||
// 新增拼团活动
|
// 新增拼团活动
|
||||||
export const createCombinationActivity = async (data: CombinationActivityVO) => {
|
export const createCombinationActivity = async (data: CombinationActivityVO) => {
|
||||||
return await request.post({ url: '/promotion/combination-activity/create', data })
|
return await request.post({ url: '/promotion/combination-activity/create', data })
|
||||||
|
|
|
@ -74,7 +74,7 @@ export function getCouponTemplatePage(params: PageParam) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获得优惠劵模板分页
|
// 获得优惠劵模板分页
|
||||||
export function getCouponTemplateList(ids: number[]) {
|
export function getCouponTemplateList(ids: number[]): Promise<CouponTemplateVO[]> {
|
||||||
return request.get({
|
return request.get({
|
||||||
url: `/promotion/coupon-template/list?ids=${ids}`
|
url: `/promotion/coupon-template/list?ids=${ids}`
|
||||||
})
|
})
|
||||||
|
|
|
@ -30,6 +30,6 @@ export const KeFuConversationApi = {
|
||||||
},
|
},
|
||||||
// 删除客服会话
|
// 删除客服会话
|
||||||
deleteConversation: async (id: number) => {
|
deleteConversation: async (id: number) => {
|
||||||
return await request.get({ url: '/promotion/kefu-conversation/delete?id' + id })
|
return await request.delete({ url: `/promotion/kefu-conversation/delete?id=${id}`})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,39 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
export interface DiscountActivityVO {
|
export interface RewardActivityVO {
|
||||||
id?: number
|
id?: number
|
||||||
name?: string
|
name?: string
|
||||||
startTime?: Date
|
startTime?: Date
|
||||||
endTime?: Date
|
endTime?: Date
|
||||||
|
startAndEndTime?: Date[] // 只前端使用
|
||||||
remark?: string
|
remark?: string
|
||||||
conditionType?: number
|
conditionType?: number
|
||||||
productScope?: number
|
productScope?: number
|
||||||
|
rules: RewardRule[]
|
||||||
|
// 如下仅用于表单,不提交
|
||||||
|
productScopeValues?: number[] // 商品范围:值为品类编号列表、商品编号列表
|
||||||
|
productCategoryIds?: number[]
|
||||||
productSpuIds?: number[]
|
productSpuIds?: number[]
|
||||||
rules?: DiscountProductVO[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优惠规则
|
// 优惠规则
|
||||||
export interface DiscountProductVO {
|
export interface RewardRule {
|
||||||
limit: number
|
limit?: number
|
||||||
discountPrice: number
|
discountPrice?: number
|
||||||
freeDelivery: boolean
|
freeDelivery?: boolean
|
||||||
point: number
|
point: number
|
||||||
couponIds: number[]
|
giveCouponTemplateCounts?: {
|
||||||
couponCounts: number[]
|
[key: number]: number
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增满减送活动
|
// 新增满减送活动
|
||||||
export const createRewardActivity = async (data: DiscountActivityVO) => {
|
export const createRewardActivity = async (data: RewardActivityVO) => {
|
||||||
return await request.post({ url: '/promotion/reward-activity/create', data })
|
return await request.post({ url: '/promotion/reward-activity/create', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新满减送活动
|
// 更新满减送活动
|
||||||
export const updateRewardActivity = async (data: DiscountActivityVO) => {
|
export const updateRewardActivity = async (data: RewardActivityVO) => {
|
||||||
return await request.put({ url: '/promotion/reward-activity/update', data })
|
return await request.put({ url: '/promotion/reward-activity/update', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,8 +46,3 @@ export const updateUserLevel = async (data: any) => {
|
||||||
export const updateUserPoint = async (data: any) => {
|
export const updateUserPoint = async (data: any) => {
|
||||||
return await request.put({ url: `/member/user/update-point`, data })
|
return await request.put({ url: `/member/user/update-point`, data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改会员用户余额
|
|
||||||
export const updateUserBalance = async (data: any) => {
|
|
||||||
return await request.put({ url: `/member/user/update-balance`, data })
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -4,6 +4,7 @@ import request from '@/config/axios'
|
||||||
export interface PayWalletUserReqVO {
|
export interface PayWalletUserReqVO {
|
||||||
userId: number
|
userId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 钱包 VO */
|
/** 钱包 VO */
|
||||||
export interface WalletVO {
|
export interface WalletVO {
|
||||||
id: number
|
id: number
|
||||||
|
@ -20,7 +21,12 @@ export const getWallet = async (params: PayWalletUserReqVO) => {
|
||||||
return await request.get<WalletVO>({ url: `/pay/wallet/get`, params })
|
return await request.get<WalletVO>({ url: `/pay/wallet/get`, params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询会员钱包列表
|
/** 查询会员钱包列表 */
|
||||||
export const getWalletPage = async (params) => {
|
export const getWalletPage = async (params: any) => {
|
||||||
return await request.get({ url: `/pay/wallet/page`, params })
|
return await request.get({ url: `/pay/wallet/page`, params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 修改会员钱包余额 */
|
||||||
|
export const updateWalletBalance = async (data: any) => {
|
||||||
|
return await request.put({ url: `/pay/wallet/update-balance`, data })
|
||||||
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.4 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 10 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724297262365" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1396" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M707.91 103c16.28 0 29.522 13.007 29.897 29.195l0.009 0.706v111.878a29.96 29.96 0 0 1-0.898 7.3l171.177-0.001c16.28 0 29.522 13.007 29.897 29.195l0.008 0.706v637.12c0 16.278-13.01 29.518-29.2 29.893l-0.705 0.008H270.884c-16.28 0-29.522-13.007-29.897-29.195l-0.008-0.706V787.274c0-16.514 13.389-29.9 29.905-29.9 16.28 0 29.522 13.007 29.897 29.194l0.008 0.706v101.924h577.4V311.88h-577.4v88.787c0 16.278-13.009 29.518-29.2 29.893l-0.705 0.008c-16.28 0-29.522-13.008-29.897-29.195l-0.008-0.706V281.979c0-16.278 13.009-29.518 29.2-29.893l0.705-0.008h408.019a29.916 29.916 0 0 1-0.89-6.593l-0.008-0.706v-81.978H132.808v407.113h385.787L408.223 456.982c-11.36-11.624-11.329-30.143-0.066-41.729l0.554-0.555c11.625-11.358 30.147-11.327 41.734-0.066l0.555 0.554 161.028 164.762c11.244 11.504 11.344 29.793 0.362 41.42l-0.55 0.565-161.027 161.849c-11.648 11.707-30.583 11.757-42.292 0.11-11.524-11.461-11.754-29.979-0.657-41.723l0.546-0.563 111.319-111.89H102.905c-16.28 0-29.522-13.007-29.897-29.195l-0.008-0.705V132.9c0-16.278 13.01-29.518 29.2-29.893l0.705-0.008H707.91z" p-id="1397"></path></svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -548,10 +548,10 @@ const inputChange = () => {
|
||||||
<el-form>
|
<el-form>
|
||||||
<el-form-item label="类型">
|
<el-form-item label="类型">
|
||||||
<el-radio-group v-model="cronValue.second.type">
|
<el-radio-group v-model="cronValue.second.type">
|
||||||
<el-radio-button label="0">任意值</el-radio-button>
|
<el-radio-button value="0">任意值</el-radio-button>
|
||||||
<el-radio-button label="1">范围</el-radio-button>
|
<el-radio-button value="1">范围</el-radio-button>
|
||||||
<el-radio-button label="2">间隔</el-radio-button>
|
<el-radio-button value="2">间隔</el-radio-button>
|
||||||
<el-radio-button label="3">指定</el-radio-button>
|
<el-radio-button value="3">指定</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="cronValue.second.type == '1'" label="范围">
|
<el-form-item v-if="cronValue.second.type == '1'" label="范围">
|
||||||
|
@ -607,10 +607,10 @@ const inputChange = () => {
|
||||||
<el-form>
|
<el-form>
|
||||||
<el-form-item label="类型">
|
<el-form-item label="类型">
|
||||||
<el-radio-group v-model="cronValue.minute.type">
|
<el-radio-group v-model="cronValue.minute.type">
|
||||||
<el-radio-button label="0">任意值</el-radio-button>
|
<el-radio-button value="0">任意值</el-radio-button>
|
||||||
<el-radio-button label="1">范围</el-radio-button>
|
<el-radio-button value="1">范围</el-radio-button>
|
||||||
<el-radio-button label="2">间隔</el-radio-button>
|
<el-radio-button value="2">间隔</el-radio-button>
|
||||||
<el-radio-button label="3">指定</el-radio-button>
|
<el-radio-button value="3">指定</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="cronValue.minute.type == '1'" label="范围">
|
<el-form-item v-if="cronValue.minute.type == '1'" label="范围">
|
||||||
|
@ -666,10 +666,10 @@ const inputChange = () => {
|
||||||
<el-form>
|
<el-form>
|
||||||
<el-form-item label="类型">
|
<el-form-item label="类型">
|
||||||
<el-radio-group v-model="cronValue.hour.type">
|
<el-radio-group v-model="cronValue.hour.type">
|
||||||
<el-radio-button label="0">任意值</el-radio-button>
|
<el-radio-button value="0">任意值</el-radio-button>
|
||||||
<el-radio-button label="1">范围</el-radio-button>
|
<el-radio-button value="1">范围</el-radio-button>
|
||||||
<el-radio-button label="2">间隔</el-radio-button>
|
<el-radio-button value="2">间隔</el-radio-button>
|
||||||
<el-radio-button label="3">指定</el-radio-button>
|
<el-radio-button value="3">指定</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="cronValue.hour.type == '1'" label="范围">
|
<el-form-item v-if="cronValue.hour.type == '1'" label="范围">
|
||||||
|
@ -725,12 +725,12 @@ const inputChange = () => {
|
||||||
<el-form>
|
<el-form>
|
||||||
<el-form-item label="类型">
|
<el-form-item label="类型">
|
||||||
<el-radio-group v-model="cronValue.day.type">
|
<el-radio-group v-model="cronValue.day.type">
|
||||||
<el-radio-button label="0">任意值</el-radio-button>
|
<el-radio-button value="0">任意值</el-radio-button>
|
||||||
<el-radio-button label="1">范围</el-radio-button>
|
<el-radio-button value="1">范围</el-radio-button>
|
||||||
<el-radio-button label="2">间隔</el-radio-button>
|
<el-radio-button value="2">间隔</el-radio-button>
|
||||||
<el-radio-button label="3">指定</el-radio-button>
|
<el-radio-button value="3">指定</el-radio-button>
|
||||||
<el-radio-button label="4">本月最后一天</el-radio-button>
|
<el-radio-button value="4">本月最后一天</el-radio-button>
|
||||||
<el-radio-button label="5">不指定</el-radio-button>
|
<el-radio-button value="5">不指定</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="cronValue.day.type == '1'" label="范围">
|
<el-form-item v-if="cronValue.day.type == '1'" label="范围">
|
||||||
|
@ -786,10 +786,10 @@ const inputChange = () => {
|
||||||
<el-form>
|
<el-form>
|
||||||
<el-form-item label="类型">
|
<el-form-item label="类型">
|
||||||
<el-radio-group v-model="cronValue.month.type">
|
<el-radio-group v-model="cronValue.month.type">
|
||||||
<el-radio-button label="0">任意值</el-radio-button>
|
<el-radio-button value="0">任意值</el-radio-button>
|
||||||
<el-radio-button label="1">范围</el-radio-button>
|
<el-radio-button value="1">范围</el-radio-button>
|
||||||
<el-radio-button label="2">间隔</el-radio-button>
|
<el-radio-button value="2">间隔</el-radio-button>
|
||||||
<el-radio-button label="3">指定</el-radio-button>
|
<el-radio-button value="3">指定</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="cronValue.month.type == '1'" label="范围">
|
<el-form-item v-if="cronValue.month.type == '1'" label="范围">
|
||||||
|
@ -846,12 +846,12 @@ const inputChange = () => {
|
||||||
<el-form>
|
<el-form>
|
||||||
<el-form-item label="类型">
|
<el-form-item label="类型">
|
||||||
<el-radio-group v-model="cronValue.week.type">
|
<el-radio-group v-model="cronValue.week.type">
|
||||||
<el-radio-button label="0">任意值</el-radio-button>
|
<el-radio-button value="0">任意值</el-radio-button>
|
||||||
<el-radio-button label="1">范围</el-radio-button>
|
<el-radio-button value="1">范围</el-radio-button>
|
||||||
<el-radio-button label="2">间隔</el-radio-button>
|
<el-radio-button value="2">间隔</el-radio-button>
|
||||||
<el-radio-button label="3">指定</el-radio-button>
|
<el-radio-button value="3">指定</el-radio-button>
|
||||||
<el-radio-button label="4">本月最后一周</el-radio-button>
|
<el-radio-button value="4">本月最后一周</el-radio-button>
|
||||||
<el-radio-button label="5">不指定</el-radio-button>
|
<el-radio-button value="5">不指定</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="cronValue.week.type == '1'" label="范围">
|
<el-form-item v-if="cronValue.week.type == '1'" label="范围">
|
||||||
|
@ -925,11 +925,11 @@ const inputChange = () => {
|
||||||
<el-form>
|
<el-form>
|
||||||
<el-form-item label="类型">
|
<el-form-item label="类型">
|
||||||
<el-radio-group v-model="cronValue.year.type">
|
<el-radio-group v-model="cronValue.year.type">
|
||||||
<el-radio-button label="-1">忽略</el-radio-button>
|
<el-radio-button value="-1">忽略</el-radio-button>
|
||||||
<el-radio-button label="0">任意值</el-radio-button>
|
<el-radio-button value="0">任意值</el-radio-button>
|
||||||
<el-radio-button label="1">范围</el-radio-button>
|
<el-radio-button value="1">范围</el-radio-button>
|
||||||
<el-radio-button label="2">间隔</el-radio-button>
|
<el-radio-button value="2">间隔</el-radio-button>
|
||||||
<el-radio-button label="3">指定</el-radio-button>
|
<el-radio-button value="3">指定</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="cronValue.year.type == '1'" label="范围">
|
<el-form-item v-if="cronValue.year.type == '1'" label="范围">
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { defineComponent, PropType, computed } 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, isString, isNumber } from '@/utils/is'
|
import { isArray, isBoolean, isNumber, isString } from '@/utils/is'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'DictTag',
|
name: 'DictTag',
|
||||||
|
@ -29,15 +29,15 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const valueArr: any = computed(() => {
|
const valueArr: any = computed(() => {
|
||||||
// 1.是Number类型的情况
|
// 1. 是 Number 类型和 Boolean 类型的情况
|
||||||
if (isNumber(props.value)) {
|
if (isNumber(props.value) || isBoolean(props.value)) {
|
||||||
return [String(props.value)]
|
return [String(props.value)]
|
||||||
}
|
}
|
||||||
// 2.是字符串(进一步判断是否有包含分隔符号 -> props.sepSymbol )
|
// 2. 是字符串(进一步判断是否有包含分隔符号 -> props.sepSymbol )
|
||||||
else if (isString(props.value)) {
|
else if (isString(props.value)) {
|
||||||
return props.value.split(props.separator)
|
return props.value.split(props.separator)
|
||||||
}
|
}
|
||||||
// 3.数组
|
// 3. 数组
|
||||||
else if (isArray(props.value)) {
|
else if (isArray(props.value)) {
|
||||||
return props.value.map(String)
|
return props.value.map(String)
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ export default defineComponent({
|
||||||
<div
|
<div
|
||||||
class="dict-tag"
|
class="dict-tag"
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'inline-flex',
|
||||||
gap: props.gutter,
|
gap: props.gutter,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
|
@ -72,7 +72,7 @@ export default defineComponent({
|
||||||
// 添加标签的文字颜色为白色,解决自定义背景颜色时标签文字看不清的问题
|
// 添加标签的文字颜色为白色,解决自定义背景颜色时标签文字看不清的问题
|
||||||
<ElTag
|
<ElTag
|
||||||
style={dict?.cssClass ? 'color: #fff' : ''}
|
style={dict?.cssClass ? 'color: #fff' : ''}
|
||||||
type={dict?.colorType}
|
type={dict?.colorType || null}
|
||||||
color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''}
|
color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''}
|
||||||
disableTransitions={true}
|
disableTransitions={true}
|
||||||
>
|
>
|
||||||
|
|
|
@ -165,6 +165,7 @@ $toolbar-position: -55px;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
color: #6a6a6a;
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
<el-form :model="formData" label-width="80px">
|
<el-form :model="formData" label-width="80px">
|
||||||
<el-form-item label="组件背景" prop="bgType">
|
<el-form-item label="组件背景" prop="bgType">
|
||||||
<el-radio-group v-model="formData.bgType">
|
<el-radio-group v-model="formData.bgType">
|
||||||
<el-radio label="color">纯色</el-radio>
|
<el-radio value="color">纯色</el-radio>
|
||||||
<el-radio label="img">图片</el-radio>
|
<el-radio value="img">图片</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="选择颜色" prop="bgColor" v-if="formData.bgType === 'color'">
|
<el-form-item label="选择颜色" prop="bgColor" v-if="formData.bgType === 'color'">
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
<el-form-item label="样式" prop="type">
|
<el-form-item label="样式" prop="type">
|
||||||
<el-radio-group v-model="formData.type">
|
<el-radio-group v-model="formData.type">
|
||||||
<el-tooltip class="item" content="默认" placement="bottom">
|
<el-tooltip class="item" content="默认" placement="bottom">
|
||||||
<el-radio-button label="default">
|
<el-radio-button value="default">
|
||||||
<Icon icon="system-uicons:carousel" />
|
<Icon icon="system-uicons:carousel" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="item" content="卡片" placement="bottom">
|
<el-tooltip class="item" content="卡片" placement="bottom">
|
||||||
<el-radio-button label="card">
|
<el-radio-button value="card">
|
||||||
<Icon icon="ic:round-view-carousel" />
|
<Icon icon="ic:round-view-carousel" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
@ -18,8 +18,8 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="指示器" prop="indicator">
|
<el-form-item label="指示器" prop="indicator">
|
||||||
<el-radio-group v-model="formData.indicator">
|
<el-radio-group v-model="formData.indicator">
|
||||||
<el-radio label="dot">小圆点</el-radio>
|
<el-radio value="dot">小圆点</el-radio>
|
||||||
<el-radio label="number">数字</el-radio>
|
<el-radio value="number">数字</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="是否轮播" prop="autoplay">
|
<el-form-item label="是否轮播" prop="autoplay">
|
||||||
|
@ -43,8 +43,8 @@
|
||||||
<template #default="{ element }">
|
<template #default="{ element }">
|
||||||
<el-form-item label="类型" prop="type" class="m-b-8px!" label-width="40px">
|
<el-form-item label="类型" prop="type" class="m-b-8px!" label-width="40px">
|
||||||
<el-radio-group v-model="element.type">
|
<el-radio-group v-model="element.type">
|
||||||
<el-radio label="img">图片</el-radio>
|
<el-radio value="img">图片</el-radio>
|
||||||
<el-radio label="video">视频</el-radio>
|
<el-radio value="video">视频</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
|
|
|
@ -26,17 +26,17 @@
|
||||||
<el-form-item label="列数" prop="type">
|
<el-form-item label="列数" prop="type">
|
||||||
<el-radio-group v-model="formData.columns">
|
<el-radio-group v-model="formData.columns">
|
||||||
<el-tooltip class="item" content="一列" placement="bottom">
|
<el-tooltip class="item" content="一列" placement="bottom">
|
||||||
<el-radio-button :label="1">
|
<el-radio-button :value="1">
|
||||||
<Icon icon="fluent:text-column-one-24-filled" />
|
<Icon icon="fluent:text-column-one-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="item" content="二列" placement="bottom">
|
<el-tooltip class="item" content="二列" placement="bottom">
|
||||||
<el-radio-button :label="2">
|
<el-radio-button :value="2">
|
||||||
<Icon icon="fluent:text-column-two-24-filled" />
|
<Icon icon="fluent:text-column-two-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="item" content="三列" placement="bottom">
|
<el-tooltip class="item" content="三列" placement="bottom">
|
||||||
<el-radio-button :label="3">
|
<el-radio-button :value="3">
|
||||||
<Icon icon="fluent:text-column-three-24-filled" />
|
<Icon icon="fluent:text-column-three-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
:key="index"
|
:key="index"
|
||||||
:content="item.text"
|
:content="item.text"
|
||||||
>
|
>
|
||||||
<el-radio-button :label="item.type">
|
<el-radio-button :value="item.type">
|
||||||
<Icon :icon="item.icon" />
|
<Icon :icon="item.icon" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
@ -24,12 +24,12 @@
|
||||||
<el-form-item label="左右边距" prop="paddingType">
|
<el-form-item label="左右边距" prop="paddingType">
|
||||||
<el-radio-group v-model="formData!.paddingType">
|
<el-radio-group v-model="formData!.paddingType">
|
||||||
<el-tooltip content="无边距" placement="top">
|
<el-tooltip content="无边距" placement="top">
|
||||||
<el-radio-button label="none">
|
<el-radio-button value="none">
|
||||||
<Icon icon="tabler:box-padding" />
|
<Icon icon="tabler:box-padding" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="左右留边" placement="top">
|
<el-tooltip content="左右留边" placement="top">
|
||||||
<el-radio-button label="horizontal">
|
<el-radio-button value="horizontal">
|
||||||
<Icon icon="vaadin:padding" />
|
<Icon icon="vaadin:padding" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
<el-card header="按钮配置" class="property-group" shadow="never">
|
<el-card header="按钮配置" class="property-group" shadow="never">
|
||||||
<el-form-item label="展开方向" prop="direction">
|
<el-form-item label="展开方向" prop="direction">
|
||||||
<el-radio-group v-model="formData.direction">
|
<el-radio-group v-model="formData.direction">
|
||||||
<el-radio label="vertical">垂直</el-radio>
|
<el-radio value="vertical">垂直</el-radio>
|
||||||
<el-radio label="horizontal">水平</el-radio>
|
<el-radio value="horizontal">水平</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="显示文字" prop="showText">
|
<el-form-item label="显示文字" prop="showText">
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
||||||
<el-form-item label="每行数量" prop="column">
|
<el-form-item label="每行数量" prop="column">
|
||||||
<el-radio-group v-model="formData.column">
|
<el-radio-group v-model="formData.column">
|
||||||
<el-radio :label="3">3个</el-radio>
|
<el-radio :value="3">3个</el-radio>
|
||||||
<el-radio :label="4">4个</el-radio>
|
<el-radio :value="4">4个</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|
|
@ -4,21 +4,21 @@
|
||||||
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
||||||
<el-form-item label="布局" prop="layout">
|
<el-form-item label="布局" prop="layout">
|
||||||
<el-radio-group v-model="formData.layout">
|
<el-radio-group v-model="formData.layout">
|
||||||
<el-radio label="iconText">图标+文字</el-radio>
|
<el-radio value="iconText">图标+文字</el-radio>
|
||||||
<el-radio label="icon">仅图标</el-radio>
|
<el-radio value="icon">仅图标</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="行数" prop="row">
|
<el-form-item label="行数" prop="row">
|
||||||
<el-radio-group v-model="formData.row">
|
<el-radio-group v-model="formData.row">
|
||||||
<el-radio :label="1">1行</el-radio>
|
<el-radio :value="1">1行</el-radio>
|
||||||
<el-radio :label="2">2行</el-radio>
|
<el-radio :value="2">2行</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="列数" prop="column">
|
<el-form-item label="列数" prop="column">
|
||||||
<el-radio-group v-model="formData.column">
|
<el-radio-group v-model="formData.column">
|
||||||
<el-radio :label="3">3列</el-radio>
|
<el-radio :value="3">3列</el-radio>
|
||||||
<el-radio :label="4">4列</el-radio>
|
<el-radio :value="4">4列</el-radio>
|
||||||
<el-radio :label="5">5列</el-radio>
|
<el-radio :value="5">5列</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
<template v-if="selectedHotAreaIndex === cellIndex">
|
<template v-if="selectedHotAreaIndex === cellIndex">
|
||||||
<el-form-item label="类型" :prop="`cell[${cellIndex}].type`">
|
<el-form-item label="类型" :prop="`cell[${cellIndex}].type`">
|
||||||
<el-radio-group v-model="cell.type">
|
<el-radio-group v-model="cell.type">
|
||||||
<el-radio label="text">文字</el-radio>
|
<el-radio value="text">文字</el-radio>
|
||||||
<el-radio label="image">图片</el-radio>
|
<el-radio value="image">图片</el-radio>
|
||||||
<el-radio label="search">搜索框</el-radio>
|
<el-radio value="search">搜索框</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 1. 文字 -->
|
<!-- 1. 文字 -->
|
||||||
|
|
|
@ -2,27 +2,27 @@
|
||||||
<el-form label-width="80px" :model="formData" :rules="rules">
|
<el-form label-width="80px" :model="formData" :rules="rules">
|
||||||
<el-form-item label="样式" prop="styleType">
|
<el-form-item label="样式" prop="styleType">
|
||||||
<el-radio-group v-model="formData!.styleType">
|
<el-radio-group v-model="formData!.styleType">
|
||||||
<el-radio label="normal">标准</el-radio>
|
<el-radio value="normal">标准</el-radio>
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
content="沉侵式头部仅支持微信小程序、APP,建议页面第一个组件为图片展示类组件"
|
content="沉侵式头部仅支持微信小程序、APP,建议页面第一个组件为图片展示类组件"
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<el-radio label="inner">沉浸式</el-radio>
|
<el-radio value="inner">沉浸式</el-radio>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="常驻显示" prop="alwaysShow" v-if="formData.styleType === 'inner'">
|
<el-form-item label="常驻显示" prop="alwaysShow" v-if="formData.styleType === 'inner'">
|
||||||
<el-radio-group v-model="formData!.alwaysShow">
|
<el-radio-group v-model="formData!.alwaysShow">
|
||||||
<el-radio :label="false">关闭</el-radio>
|
<el-radio :value="false">关闭</el-radio>
|
||||||
<el-tooltip content="常驻显示关闭后,头部小组件将在页面滑动时淡入" placement="top">
|
<el-tooltip content="常驻显示关闭后,头部小组件将在页面滑动时淡入" placement="top">
|
||||||
<el-radio :label="true">开启</el-radio>
|
<el-radio :value="true">开启</el-radio>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="背景类型" prop="bgType">
|
<el-form-item label="背景类型" prop="bgType">
|
||||||
<el-radio-group v-model="formData.bgType">
|
<el-radio-group v-model="formData.bgType">
|
||||||
<el-radio label="color">纯色</el-radio>
|
<el-radio value="color">纯色</el-radio>
|
||||||
<el-radio label="img">图片</el-radio>
|
<el-radio value="img">图片</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="背景颜色" prop="bgColor" v-if="formData.bgType === 'color'">
|
<el-form-item label="背景颜色" prop="bgColor" v-if="formData.bgType === 'color'">
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
<el-form-item label="显示次数" :prop="`list[${index}].showType`">
|
<el-form-item label="显示次数" :prop="`list[${index}].showType`">
|
||||||
<el-radio-group v-model="element.showType">
|
<el-radio-group v-model="element.showType">
|
||||||
<el-tooltip content="只显示一次,下次打开时不显示" placement="bottom">
|
<el-tooltip content="只显示一次,下次打开时不显示" placement="bottom">
|
||||||
<el-radio label="once">一次</el-radio>
|
<el-radio value="once">一次</el-radio>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="每次打开时都会显示" placement="bottom">
|
<el-tooltip content="每次打开时都会显示" placement="bottom">
|
||||||
<el-radio label="always">不限</el-radio>
|
<el-radio value="always">不限</el-radio>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
|
@ -67,15 +67,15 @@
|
||||||
class="text-16px"
|
class="text-16px"
|
||||||
:style="{ color: property.fields.price.color }"
|
:style="{ color: property.fields.price.color }"
|
||||||
>
|
>
|
||||||
¥{{ spu.price }}
|
¥{{ fenToYuan(spu.price) }}
|
||||||
</span>
|
</span>
|
||||||
<!-- 市场价 -->
|
<!-- 市场价 -->
|
||||||
<span
|
<span
|
||||||
v-if="property.fields.marketPrice.show && spu.marketPrice"
|
v-if="property.fields.marketPrice.show && spu.marketPrice"
|
||||||
class="ml-4px text-10px line-through"
|
class="ml-4px text-10px line-through"
|
||||||
:style="{ color: property.fields.marketPrice.color }"
|
:style="{ color: property.fields.marketPrice.color }"
|
||||||
>¥{{ spu.marketPrice }}</span
|
>¥{{ fenToYuan(spu.marketPrice) }}
|
||||||
>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-12px">
|
<div class="text-12px">
|
||||||
<!-- 销量 -->
|
<!-- 销量 -->
|
||||||
|
@ -117,6 +117,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProductCardProperty } from './config'
|
import { ProductCardProperty } from './config'
|
||||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||||
|
import { fenToYuan } from '../../../../../utils'
|
||||||
|
|
||||||
/** 商品卡片 */
|
/** 商品卡片 */
|
||||||
defineOptions({ name: 'ProductCard' })
|
defineOptions({ name: 'ProductCard' })
|
||||||
|
|
|
@ -8,17 +8,17 @@
|
||||||
<el-form-item label="布局" prop="type">
|
<el-form-item label="布局" prop="type">
|
||||||
<el-radio-group v-model="formData.layoutType">
|
<el-radio-group v-model="formData.layoutType">
|
||||||
<el-tooltip class="item" content="单列大图" placement="bottom">
|
<el-tooltip class="item" content="单列大图" placement="bottom">
|
||||||
<el-radio-button label="oneColBigImg">
|
<el-radio-button value="oneColBigImg">
|
||||||
<Icon icon="fluent:text-column-one-24-filled" />
|
<Icon icon="fluent:text-column-one-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="item" content="单列小图" placement="bottom">
|
<el-tooltip class="item" content="单列小图" placement="bottom">
|
||||||
<el-radio-button label="oneColSmallImg">
|
<el-radio-button value="oneColSmallImg">
|
||||||
<Icon icon="fluent:text-column-two-left-24-filled" />
|
<Icon icon="fluent:text-column-two-left-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="item" content="双列" placement="bottom">
|
<el-tooltip class="item" content="双列" placement="bottom">
|
||||||
<el-radio-button label="twoCol">
|
<el-radio-button value="twoCol">
|
||||||
<Icon icon="fluent:text-column-two-24-filled" />
|
<Icon icon="fluent:text-column-two-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
@ -74,8 +74,8 @@
|
||||||
<el-card header="按钮" class="property-group" shadow="never">
|
<el-card header="按钮" class="property-group" shadow="never">
|
||||||
<el-form-item label="按钮类型" prop="btnBuy.type">
|
<el-form-item label="按钮类型" prop="btnBuy.type">
|
||||||
<el-radio-group v-model="formData.btnBuy.type">
|
<el-radio-group v-model="formData.btnBuy.type">
|
||||||
<el-radio-button label="text">文字</el-radio-button>
|
<el-radio-button value="text">文字</el-radio-button>
|
||||||
<el-radio-button label="img">图片</el-radio-button>
|
<el-radio-button value="img">图片</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<template v-if="formData.btnBuy.type === 'text'">
|
<template v-if="formData.btnBuy.type === 'text'">
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
class="text-12px"
|
class="text-12px"
|
||||||
:style="{ color: property.fields.price.color }"
|
:style="{ color: property.fields.price.color }"
|
||||||
>
|
>
|
||||||
¥{{ spu.price }}
|
¥{{ fenToYuan(spu.price) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,6 +65,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProductListProperty } from './config'
|
import { ProductListProperty } from './config'
|
||||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||||
|
import { fenToYuan } from './index'
|
||||||
|
|
||||||
/** 商品栏 */
|
/** 商品栏 */
|
||||||
defineOptions({ name: 'ProductList' })
|
defineOptions({ name: 'ProductList' })
|
||||||
|
|
|
@ -8,17 +8,17 @@
|
||||||
<el-form-item label="布局" prop="type">
|
<el-form-item label="布局" prop="type">
|
||||||
<el-radio-group v-model="formData.layoutType">
|
<el-radio-group v-model="formData.layoutType">
|
||||||
<el-tooltip class="item" content="双列" placement="bottom">
|
<el-tooltip class="item" content="双列" placement="bottom">
|
||||||
<el-radio-button label="twoCol">
|
<el-radio-button value="twoCol">
|
||||||
<Icon icon="fluent:text-column-two-24-filled" />
|
<Icon icon="fluent:text-column-two-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="item" content="三列" placement="bottom">
|
<el-tooltip class="item" content="三列" placement="bottom">
|
||||||
<el-radio-button label="threeCol">
|
<el-radio-button value="threeCol">
|
||||||
<Icon icon="fluent:text-column-three-24-filled" />
|
<Icon icon="fluent:text-column-three-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="item" content="水平滑动" placement="bottom">
|
<el-tooltip class="item" content="水平滑动" placement="bottom">
|
||||||
<el-radio-button label="horizSwiper">
|
<el-radio-button value="horizSwiper">
|
||||||
<Icon icon="system-uicons:carousel" />
|
<Icon icon="system-uicons:carousel" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
|
@ -3,13 +3,21 @@ import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
/** 拼团属性 */
|
/** 拼团属性 */
|
||||||
export interface PromotionCombinationProperty {
|
export interface PromotionCombinationProperty {
|
||||||
// 布局类型:单列 | 三列
|
// 布局类型:单列 | 三列
|
||||||
layoutType: 'oneCol' | 'threeCol'
|
layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
|
||||||
// 商品字段
|
// 商品字段
|
||||||
fields: {
|
fields: {
|
||||||
// 商品名称
|
// 商品名称
|
||||||
name: PromotionCombinationFieldProperty
|
name: PromotionCombinationFieldProperty
|
||||||
|
// 商品简介
|
||||||
|
introduction: PromotionCombinationFieldProperty
|
||||||
// 商品价格
|
// 商品价格
|
||||||
price: PromotionCombinationFieldProperty
|
price: PromotionCombinationFieldProperty
|
||||||
|
// 市场价
|
||||||
|
marketPrice: PromotionCombinationFieldProperty
|
||||||
|
// 商品销量
|
||||||
|
salesCount: PromotionCombinationFieldProperty
|
||||||
|
// 商品库存
|
||||||
|
stock: PromotionCombinationFieldProperty
|
||||||
}
|
}
|
||||||
// 角标
|
// 角标
|
||||||
badge: {
|
badge: {
|
||||||
|
@ -18,6 +26,19 @@ export interface PromotionCombinationProperty {
|
||||||
// 角标图片
|
// 角标图片
|
||||||
imgUrl: string
|
imgUrl: string
|
||||||
}
|
}
|
||||||
|
// 按钮
|
||||||
|
btnBuy: {
|
||||||
|
// 类型:文字 | 图片
|
||||||
|
type: 'text' | 'img'
|
||||||
|
// 文字
|
||||||
|
text: string
|
||||||
|
// 文字按钮:背景渐变起始颜色
|
||||||
|
bgBeginColor: string
|
||||||
|
// 文字按钮:背景渐变结束颜色
|
||||||
|
bgEndColor: string
|
||||||
|
// 图片按钮:图片地址
|
||||||
|
imgUrl: string
|
||||||
|
}
|
||||||
// 上圆角
|
// 上圆角
|
||||||
borderRadiusTop: number
|
borderRadiusTop: number
|
||||||
// 下圆角
|
// 下圆角
|
||||||
|
@ -25,7 +46,7 @@ export interface PromotionCombinationProperty {
|
||||||
// 间距
|
// 间距
|
||||||
space: number
|
space: number
|
||||||
// 拼团活动编号
|
// 拼团活动编号
|
||||||
activityId: number
|
activityIds: number[]
|
||||||
// 组件样式
|
// 组件样式
|
||||||
style: ComponentStyle
|
style: ComponentStyle
|
||||||
}
|
}
|
||||||
|
@ -44,12 +65,23 @@ export const component = {
|
||||||
name: '拼团',
|
name: '拼团',
|
||||||
icon: 'mdi:account-group',
|
icon: 'mdi:account-group',
|
||||||
property: {
|
property: {
|
||||||
layoutType: 'oneCol',
|
layoutType: 'oneColBigImg',
|
||||||
fields: {
|
fields: {
|
||||||
name: { show: true, color: '#000' },
|
name: { show: true, color: '#000' },
|
||||||
price: { show: true, color: '#ff3000' }
|
introduction: { show: true, color: '#999' },
|
||||||
|
price: { show: true, color: '#ff3000' },
|
||||||
|
marketPrice: { show: true, color: '#c4c4c4' },
|
||||||
|
salesCount: { show: true, color: '#c4c4c4' },
|
||||||
|
stock: { show: false, color: '#c4c4c4' }
|
||||||
},
|
},
|
||||||
badge: { show: false, imgUrl: '' },
|
badge: { show: false, imgUrl: '' },
|
||||||
|
btnBuy: {
|
||||||
|
type: 'text',
|
||||||
|
text: '去拼团',
|
||||||
|
bgBeginColor: '#FF6000',
|
||||||
|
bgEndColor: '#FE832A',
|
||||||
|
imgUrl: ''
|
||||||
|
},
|
||||||
borderRadiusTop: 8,
|
borderRadiusTop: 8,
|
||||||
borderRadiusBottom: 8,
|
borderRadiusBottom: 8,
|
||||||
space: 8,
|
space: 8,
|
||||||
|
|
|
@ -1,125 +1,201 @@
|
||||||
<template>
|
<template>
|
||||||
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
|
<div :class="`box-content min-h-30px w-full flex flex-row flex-wrap`" ref="containerRef">
|
||||||
<!-- 商品网格 -->
|
|
||||||
<div
|
<div
|
||||||
class="grid overflow-x-auto"
|
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
|
||||||
:style="{
|
:style="{
|
||||||
gridGap: `${property.space}px`,
|
...calculateSpace(index),
|
||||||
gridTemplateColumns,
|
...calculateWidth(),
|
||||||
width: scrollbarWidth
|
borderTopLeftRadius: `${property.borderRadiusTop}px`,
|
||||||
|
borderTopRightRadius: `${property.borderRadiusTop}px`,
|
||||||
|
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
|
||||||
|
borderBottomRightRadius: `${property.borderRadiusBottom}px`
|
||||||
}"
|
}"
|
||||||
|
v-for="(spu, index) in spuList"
|
||||||
|
:key="index"
|
||||||
>
|
>
|
||||||
<!-- 商品 -->
|
<!-- 角标 -->
|
||||||
|
<div v-if="property.badge.show" 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" />
|
||||||
|
</div>
|
||||||
|
<!-- 商品封面图 -->
|
||||||
<div
|
<div
|
||||||
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
|
:class="[
|
||||||
:style="{
|
'h-140px',
|
||||||
borderTopLeftRadius: `${property.borderRadiusTop}px`,
|
{
|
||||||
borderTopRightRadius: `${property.borderRadiusTop}px`,
|
'w-full': property.layoutType !== 'oneColSmallImg',
|
||||||
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
|
'w-140px': property.layoutType === 'oneColSmallImg'
|
||||||
borderBottomRightRadius: `${property.borderRadiusBottom}px`
|
}
|
||||||
}"
|
]"
|
||||||
v-for="(spu, index) in spuList"
|
|
||||||
:key="index"
|
|
||||||
>
|
>
|
||||||
<!-- 角标 -->
|
<el-image fit="cover" class="h-full w-full" :src="spu.picUrl" />
|
||||||
<div
|
</div>
|
||||||
v-if="property.badge.show"
|
<div
|
||||||
class="absolute left-0 top-0 z-1 items-center justify-center"
|
:class="[
|
||||||
>
|
' flex flex-col gap-8px p-8px box-border',
|
||||||
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
|
{
|
||||||
</div>
|
'w-full': property.layoutType !== 'oneColSmallImg',
|
||||||
<!-- 商品封面图 -->
|
'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
|
||||||
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<!-- 商品名称 -->
|
||||||
<div
|
<div
|
||||||
|
v-if="property.fields.name.show"
|
||||||
:class="[
|
:class="[
|
||||||
'flex flex-col gap-8px p-8px box-border',
|
'text-14px ',
|
||||||
{
|
{
|
||||||
'w-[calc(100%-64px)]': columns === 2,
|
truncate: property.layoutType !== 'oneColSmallImg',
|
||||||
'w-full': columns === 3
|
'overflow-ellipsis line-clamp-2': property.layoutType === 'oneColSmallImg'
|
||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
|
:style="{ color: property.fields.name.color }"
|
||||||
>
|
>
|
||||||
<!-- 商品名称 -->
|
{{ spu.name }}
|
||||||
<div
|
</div>
|
||||||
v-if="property.fields.name.show"
|
<!-- 商品简介 -->
|
||||||
class="truncate text-12px"
|
<div
|
||||||
:style="{ color: property.fields.name.color }"
|
v-if="property.fields.introduction.show"
|
||||||
|
class="truncate text-12px"
|
||||||
|
:style="{ color: property.fields.introduction.color }"
|
||||||
|
>
|
||||||
|
{{ spu.introduction }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<!-- 价格 -->
|
||||||
|
<span
|
||||||
|
v-if="property.fields.price.show"
|
||||||
|
class="text-16px"
|
||||||
|
:style="{ color: property.fields.price.color }"
|
||||||
>
|
>
|
||||||
{{ spu.name }}
|
¥{{ fenToYuan(spu.price || Infinity) }}
|
||||||
</div>
|
</span>
|
||||||
<div>
|
<!-- 市场价 -->
|
||||||
<!-- 商品价格 -->
|
<span
|
||||||
<span
|
v-if="property.fields.marketPrice.show && spu.marketPrice"
|
||||||
v-if="property.fields.price.show"
|
class="ml-4px text-10px line-through"
|
||||||
class="text-12px"
|
:style="{ color: property.fields.marketPrice.color }"
|
||||||
:style="{ color: property.fields.price.color }"
|
>¥{{ fenToYuan(spu.marketPrice) }}</span
|
||||||
>
|
>
|
||||||
¥{{ spu.price }}
|
</div>
|
||||||
</span>
|
<div class="text-12px">
|
||||||
</div>
|
<!-- 销量 -->
|
||||||
|
<span
|
||||||
|
v-if="property.fields.salesCount.show"
|
||||||
|
:style="{ color: property.fields.salesCount.color }"
|
||||||
|
>
|
||||||
|
已售{{ (spu.salesCount || 0) + (spu.virtualSalesCount || 0) }}件
|
||||||
|
</span>
|
||||||
|
<!-- 库存 -->
|
||||||
|
<span v-if="property.fields.stock.show" :style="{ color: property.fields.stock.color }">
|
||||||
|
库存{{ spu.stock || 0 }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 购买按钮 -->
|
||||||
|
<div class="absolute bottom-8px right-8px">
|
||||||
|
<!-- 文字按钮 -->
|
||||||
|
<span
|
||||||
|
v-if="property.btnBuy.type === 'text'"
|
||||||
|
class="rounded-full p-x-12px p-y-4px text-12px text-white"
|
||||||
|
:style="{
|
||||||
|
background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ property.btnBuy.text }}
|
||||||
|
</span>
|
||||||
|
<!-- 图片按钮 -->
|
||||||
|
<el-image
|
||||||
|
v-else
|
||||||
|
class="h-28px w-28px rounded-full"
|
||||||
|
fit="cover"
|
||||||
|
:src="property.btnBuy.imgUrl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
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 * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
|
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
|
||||||
|
import { fenToYuan } from '@/utils'
|
||||||
|
|
||||||
/** 拼团 */
|
/** 拼团卡片 */
|
||||||
defineOptions({ name: 'PromotionCombination' })
|
defineOptions({ name: 'PromotionCombination' })
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const props = defineProps<{ property: PromotionCombinationProperty }>()
|
const props = defineProps<{ property: PromotionCombinationProperty }>()
|
||||||
// 商品列表
|
// 商品列表
|
||||||
const spuList = ref<ProductSpuApi.Spu[]>([])
|
const spuList = ref<ProductSpuApi.Spu[]>([])
|
||||||
|
const spuIdList = ref<number[]>([])
|
||||||
|
const combinationActivityList = ref<CombinationActivityApi.CombinationActivityVO[]>([])
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.property.activityId,
|
() => props.property.activityIds,
|
||||||
async () => {
|
async () => {
|
||||||
if (!props.property.activityId) return
|
try {
|
||||||
const activity = await CombinationActivityApi.getCombinationActivity(props.property.activityId)
|
// 新添加的拼团组件,是没有活动ID的
|
||||||
if (!activity?.spuId) return
|
const activityIds = props.property.activityIds
|
||||||
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
|
// 检查活动ID的有效性
|
||||||
|
if (Array.isArray(activityIds) && activityIds.length > 0) {
|
||||||
|
// 获取拼团活动详情列表
|
||||||
|
combinationActivityList.value =
|
||||||
|
await CombinationActivityApi.getCombinationActivityListByIds(activityIds)
|
||||||
|
|
||||||
|
// 获取拼团活动的 SPU 详情列表
|
||||||
|
spuList.value = []
|
||||||
|
spuIdList.value = combinationActivityList.value
|
||||||
|
.map((activity) => activity.spuId)
|
||||||
|
.filter((spuId): spuId is number => typeof spuId === 'number')
|
||||||
|
if (spuIdList.value.length > 0) {
|
||||||
|
spuList.value = await ProductSpuApi.getSpuDetailList(spuIdList.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 SPU 的最低价格
|
||||||
|
combinationActivityList.value.forEach((activity) => {
|
||||||
|
// 匹配spuId
|
||||||
|
const spu = spuList.value.find((spu) => spu.id === activity.spuId)
|
||||||
|
if (spu) {
|
||||||
|
// 赋值活动价格,哪个最便宜就赋值哪个
|
||||||
|
spu.price = Math.min(activity.combinationPrice || Infinity, spu.price || Infinity)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取拼团活动细节或 SPU 细节时出错:', error)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
deep: true
|
deep: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// 手机宽度
|
|
||||||
const phoneWidth = ref(375)
|
/**
|
||||||
|
* 计算商品的间距
|
||||||
|
* @param index 商品索引
|
||||||
|
*/
|
||||||
|
const calculateSpace = (index: number) => {
|
||||||
|
// 商品的列数
|
||||||
|
const columns = props.property.layoutType === 'twoCol' ? 2 : 1
|
||||||
|
// 第一列没有左边距
|
||||||
|
const marginLeft = index % columns === 0 ? '0' : props.property.space + 'px'
|
||||||
|
// 第一行没有上边距
|
||||||
|
const marginTop = index < columns ? '0' : props.property.space + 'px'
|
||||||
|
|
||||||
|
return { marginLeft, marginTop }
|
||||||
|
}
|
||||||
|
|
||||||
// 容器
|
// 容器
|
||||||
const containerRef = ref()
|
const containerRef = ref()
|
||||||
// 商品的列数
|
// 计算商品的宽度
|
||||||
const columns = ref(2)
|
const calculateWidth = () => {
|
||||||
// 滚动条宽度
|
let width = '100%'
|
||||||
const scrollbarWidth = ref('100%')
|
// 双列时每列的宽度为:(总宽度 - 间距)/ 2
|
||||||
// 商品图大小
|
if (props.property.layoutType === 'twoCol') {
|
||||||
const imageSize = ref('0')
|
width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`
|
||||||
// 商品网络列数
|
}
|
||||||
const gridTemplateColumns = ref('')
|
return { width }
|
||||||
// 计算布局参数
|
}
|
||||||
watch(
|
|
||||||
() => [props.property, phoneWidth, spuList.value.length],
|
|
||||||
() => {
|
|
||||||
// 计算列数
|
|
||||||
columns.value = props.property.layoutType === 'oneCol' ? 1 : 3
|
|
||||||
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
|
|
||||||
const productWidth =
|
|
||||||
(phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value
|
|
||||||
// 商品图布局:2列时,左右布局 3列时,上下布局
|
|
||||||
imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`
|
|
||||||
// 指定列数
|
|
||||||
gridTemplateColumns.value = `repeat(${columns.value}, auto)`
|
|
||||||
// 不滚动
|
|
||||||
scrollbarWidth.value = '100%'
|
|
||||||
},
|
|
||||||
{ immediate: true, deep: true }
|
|
||||||
)
|
|
||||||
onMounted(() => {
|
|
||||||
// 提取手机宽度
|
|
||||||
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|
|
@ -2,30 +2,31 @@
|
||||||
<ComponentContainerProperty v-model="formData.style">
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
<el-form label-width="80px" :model="formData">
|
<el-form label-width="80px" :model="formData">
|
||||||
<el-card header="拼团活动" class="property-group" shadow="never">
|
<el-card header="拼团活动" class="property-group" shadow="never">
|
||||||
<el-form-item label="拼团活动" prop="activityId">
|
<CombinationShowcase v-model="formData.activityIds" />
|
||||||
<el-select v-model="formData.activityId">
|
|
||||||
<el-option
|
|
||||||
v-for="activity in activityList"
|
|
||||||
:key="activity.id"
|
|
||||||
:label="activity.name"
|
|
||||||
:value="activity.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card header="商品样式" class="property-group" shadow="never">
|
<el-card header="商品样式" class="property-group" shadow="never">
|
||||||
<el-form-item label="布局" prop="type">
|
<el-form-item label="布局" prop="type">
|
||||||
<el-radio-group v-model="formData.layoutType">
|
<el-radio-group v-model="formData.layoutType">
|
||||||
<el-tooltip class="item" content="单列" placement="bottom">
|
<el-tooltip class="item" content="单列大图" placement="bottom">
|
||||||
<el-radio-button label="oneCol">
|
<el-radio-button value="oneColBigImg">
|
||||||
<Icon icon="fluent:text-column-one-24-filled" />
|
<Icon icon="fluent:text-column-one-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="item" content="三列" placement="bottom">
|
<el-tooltip class="item" content="单列小图" placement="bottom">
|
||||||
<el-radio-button label="threeCol">
|
<el-radio-button value="oneColSmallImg">
|
||||||
<Icon icon="fluent:text-column-three-24-filled" />
|
<Icon icon="fluent:text-column-two-left-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<el-tooltip class="item" content="双列" placement="bottom">
|
||||||
|
<el-radio-button value="twoCol">
|
||||||
|
<Icon icon="fluent:text-column-two-24-filled" />
|
||||||
|
</el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<!--<el-tooltip class="item" content="三列" placement="bottom">
|
||||||
|
<el-radio-button value="threeCol">
|
||||||
|
<Icon icon="fluent:text-column-three-24-filled" />
|
||||||
|
</el-radio-button>
|
||||||
|
</el-tooltip>-->
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="商品名称" prop="fields.name.show">
|
<el-form-item label="商品名称" prop="fields.name.show">
|
||||||
|
@ -34,12 +35,36 @@
|
||||||
<el-checkbox v-model="formData.fields.name.show" />
|
<el-checkbox v-model="formData.fields.name.show" />
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="商品简介" prop="fields.introduction.show">
|
||||||
|
<div class="flex gap-8px">
|
||||||
|
<ColorInput v-model="formData.fields.introduction.color" />
|
||||||
|
<el-checkbox v-model="formData.fields.introduction.show" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="商品价格" prop="fields.price.show">
|
<el-form-item label="商品价格" prop="fields.price.show">
|
||||||
<div class="flex gap-8px">
|
<div class="flex gap-8px">
|
||||||
<ColorInput v-model="formData.fields.price.color" />
|
<ColorInput v-model="formData.fields.price.color" />
|
||||||
<el-checkbox v-model="formData.fields.price.show" />
|
<el-checkbox v-model="formData.fields.price.show" />
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="市场价" prop="fields.marketPrice.show">
|
||||||
|
<div class="flex gap-8px">
|
||||||
|
<ColorInput v-model="formData.fields.marketPrice.color" />
|
||||||
|
<el-checkbox v-model="formData.fields.marketPrice.show" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品销量" prop="fields.salesCount.show">
|
||||||
|
<div class="flex gap-8px">
|
||||||
|
<ColorInput v-model="formData.fields.salesCount.color" />
|
||||||
|
<el-checkbox v-model="formData.fields.salesCount.show" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品库存" prop="fields.stock.show">
|
||||||
|
<div class="flex gap-8px">
|
||||||
|
<ColorInput v-model="formData.fields.stock.color" />
|
||||||
|
<el-checkbox v-model="formData.fields.stock.show" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card header="角标" class="property-group" shadow="never">
|
<el-card header="角标" class="property-group" shadow="never">
|
||||||
<el-form-item label="角标" prop="badge.show">
|
<el-form-item label="角标" prop="badge.show">
|
||||||
|
@ -47,10 +72,36 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
|
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
|
||||||
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
|
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
|
||||||
<template #tip> 建议尺寸:36 * 22 </template>
|
<template #tip> 建议尺寸:36 * 22</template>
|
||||||
</UploadImg>
|
</UploadImg>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
<el-card header="按钮" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="按钮类型" prop="btnBuy.type">
|
||||||
|
<el-radio-group v-model="formData.btnBuy.type">
|
||||||
|
<el-radio-button value="text">文字</el-radio-button>
|
||||||
|
<el-radio-button value="img">图片</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<template v-if="formData.btnBuy.type === 'text'">
|
||||||
|
<el-form-item label="按钮文字" prop="btnBuy.text">
|
||||||
|
<el-input v-model="formData.btnBuy.text" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="左侧背景" prop="btnBuy.bgBeginColor">
|
||||||
|
<ColorInput v-model="formData.btnBuy.bgBeginColor" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="右侧背景" prop="btnBuy.bgEndColor">
|
||||||
|
<ColorInput v-model="formData.btnBuy.bgEndColor" />
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-form-item label="图片" prop="btnBuy.imgUrl">
|
||||||
|
<UploadImg v-model="formData.btnBuy.imgUrl" height="56px" width="56px">
|
||||||
|
<template #tip> 建议尺寸:56 * 56</template>
|
||||||
|
</UploadImg>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</el-card>
|
||||||
<el-card header="商品样式" class="property-group" shadow="never">
|
<el-card header="商品样式" class="property-group" shadow="never">
|
||||||
<el-form-item label="上圆角" prop="borderRadiusTop">
|
<el-form-item label="上圆角" prop="borderRadiusTop">
|
||||||
<el-slider
|
<el-slider
|
||||||
|
@ -92,6 +143,7 @@ import { PromotionCombinationProperty } from './config'
|
||||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||||
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
|
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
import CombinationShowcase from '@/views/mall/promotion/combination/components/CombinationShowcase.vue'
|
||||||
|
|
||||||
// 拼团属性面板
|
// 拼团属性面板
|
||||||
defineOptions({ name: 'PromotionCombinationProperty' })
|
defineOptions({ name: 'PromotionCombinationProperty' })
|
||||||
|
@ -100,7 +152,7 @@ const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||||
// 活动列表
|
// 活动列表
|
||||||
const activityList = ref<CombinationActivityApi.CombinationActivityVO>([])
|
const activityList = ref<CombinationActivityApi.CombinationActivityVO[]>([])
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const { list } = await CombinationActivityApi.getCombinationActivityPage({
|
const { list } = await CombinationActivityApi.getCombinationActivityPage({
|
||||||
status: CommonStatusEnum.ENABLE
|
status: CommonStatusEnum.ENABLE
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -17,12 +17,12 @@
|
||||||
<el-form-item label="布局" prop="type">
|
<el-form-item label="布局" prop="type">
|
||||||
<el-radio-group v-model="formData.layoutType">
|
<el-radio-group v-model="formData.layoutType">
|
||||||
<el-tooltip class="item" content="单列" placement="bottom">
|
<el-tooltip class="item" content="单列" placement="bottom">
|
||||||
<el-radio-button label="oneCol">
|
<el-radio-button value="oneCol">
|
||||||
<Icon icon="fluent:text-column-one-24-filled" />
|
<Icon icon="fluent:text-column-one-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="item" content="三列" placement="bottom">
|
<el-tooltip class="item" content="三列" placement="bottom">
|
||||||
<el-radio-button label="threeCol">
|
<el-radio-button value="threeCol">
|
||||||
<Icon icon="fluent:text-column-three-24-filled" />
|
<Icon icon="fluent:text-column-three-24-filled" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
|
@ -13,12 +13,12 @@
|
||||||
<el-form-item label="框体样式">
|
<el-form-item label="框体样式">
|
||||||
<el-radio-group v-model="formData!.borderRadius">
|
<el-radio-group v-model="formData!.borderRadius">
|
||||||
<el-tooltip content="方形" placement="top">
|
<el-tooltip content="方形" placement="top">
|
||||||
<el-radio-button :label="0">
|
<el-radio-button :value="0">
|
||||||
<Icon icon="tabler:input-search" />
|
<Icon icon="tabler:input-search" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="圆形" placement="top">
|
<el-tooltip content="圆形" placement="top">
|
||||||
<el-radio-button :label="10">
|
<el-radio-button :value="10">
|
||||||
<Icon icon="iconoir:input-search" />
|
<Icon icon="iconoir:input-search" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
@ -30,12 +30,12 @@
|
||||||
<el-form-item label="文本位置" prop="placeholderPosition">
|
<el-form-item label="文本位置" prop="placeholderPosition">
|
||||||
<el-radio-group v-model="formData!.placeholderPosition">
|
<el-radio-group v-model="formData!.placeholderPosition">
|
||||||
<el-tooltip content="居左" placement="top">
|
<el-tooltip content="居左" placement="top">
|
||||||
<el-radio-button label="left">
|
<el-radio-button value="left">
|
||||||
<Icon icon="ant-design:align-left-outlined" />
|
<Icon icon="ant-design:align-left-outlined" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="居中" placement="top">
|
<el-tooltip content="居中" placement="top">
|
||||||
<el-radio-button label="center">
|
<el-radio-button value="center">
|
||||||
<Icon icon="ant-design:align-center-outlined" />
|
<Icon icon="ant-design:align-center-outlined" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
|
@ -27,8 +27,8 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="导航背景">
|
<el-form-item label="导航背景">
|
||||||
<el-radio-group v-model="formData!.style.bgType">
|
<el-radio-group v-model="formData!.style.bgType">
|
||||||
<el-radio-button label="color">纯色</el-radio-button>
|
<el-radio-button value="color">纯色</el-radio-button>
|
||||||
<el-radio-button label="img">图片</el-radio-button>
|
<el-radio-button value="img">图片</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="选择颜色" v-if="formData!.style.bgType === 'color'">
|
<el-form-item label="选择颜色" v-if="formData!.style.bgType === 'color'">
|
||||||
|
|
|
@ -10,12 +10,12 @@
|
||||||
<el-form-item label="标题位置" prop="textAlign">
|
<el-form-item label="标题位置" prop="textAlign">
|
||||||
<el-radio-group v-model="formData!.textAlign">
|
<el-radio-group v-model="formData!.textAlign">
|
||||||
<el-tooltip content="居左" placement="top">
|
<el-tooltip content="居左" placement="top">
|
||||||
<el-radio-button label="left">
|
<el-radio-button value="left">
|
||||||
<Icon icon="ant-design:align-left-outlined" />
|
<Icon icon="ant-design:align-left-outlined" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="居中" placement="top">
|
<el-tooltip content="居中" placement="top">
|
||||||
<el-radio-button label="center">
|
<el-radio-button value="center">
|
||||||
<Icon icon="ant-design:align-center-outlined" />
|
<Icon icon="ant-design:align-center-outlined" />
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
@ -88,9 +88,9 @@
|
||||||
<template v-if="formData.more.show">
|
<template v-if="formData.more.show">
|
||||||
<el-form-item label="样式" prop="more.type">
|
<el-form-item label="样式" prop="more.type">
|
||||||
<el-radio-group v-model="formData.more.type">
|
<el-radio-group v-model="formData.more.type">
|
||||||
<el-radio label="text">文字</el-radio>
|
<el-radio value="text">文字</el-radio>
|
||||||
<el-radio label="icon">图标</el-radio>
|
<el-radio value="icon">图标</el-radio>
|
||||||
<el-radio label="all">文字+图标</el-radio>
|
<el-radio value="all">文字+图标</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
|
<el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
|
||||||
|
|
|
@ -13,9 +13,9 @@
|
||||||
class="mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px"
|
class="mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px"
|
||||||
>
|
>
|
||||||
<!-- 操作按钮区 -->
|
<!-- 操作按钮区 -->
|
||||||
<div class="m--8px m-b-4px flex flex-row items-center justify-between bg-gray-1 p-8px">
|
<div class="m--8px m-b-4px flex flex-row items-center justify-between p-8px" style="background-color: var(--app-content-bg-color);">
|
||||||
<el-tooltip content="拖动排序">
|
<el-tooltip content="拖动排序">
|
||||||
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
|
<Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" style="color: #8a909c;" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="删除">
|
<el-tooltip content="删除">
|
||||||
<Icon
|
<Icon
|
||||||
|
|
|
@ -7,26 +7,41 @@ const props = defineProps({
|
||||||
src: propTypes.string.def('')
|
src: propTypes.string.def('')
|
||||||
})
|
})
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const height = ref('')
|
|
||||||
const frameRef = ref<HTMLElement | null>(null)
|
const frameRef = ref<HTMLElement | null>(null)
|
||||||
const init = () => {
|
const init = () => {
|
||||||
height.value = document.documentElement.clientHeight - 94.5 + 'px'
|
nextTick(() => {
|
||||||
loading.value = false
|
loading.value = true
|
||||||
|
if (!frameRef.value) return
|
||||||
|
frameRef.value.onload = () => {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
init()
|
||||||
init()
|
|
||||||
}, 300)
|
|
||||||
})
|
})
|
||||||
|
watch(
|
||||||
|
() => props.src,
|
||||||
|
() => {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-loading="loading" :style="'height:' + height">
|
<div
|
||||||
|
v-loading="loading"
|
||||||
|
class="w-full h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-2px)]"
|
||||||
|
>
|
||||||
<iframe
|
<iframe
|
||||||
ref="frameRef"
|
ref="frameRef"
|
||||||
:src="props.src"
|
:src="props.src"
|
||||||
frameborder="no"
|
frameborder="0"
|
||||||
scrolling="auto"
|
scrolling="auto"
|
||||||
style="width: 100%; height: 100%"
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
allowfullscreen="true"
|
||||||
|
webkitallowfullscreen="true"
|
||||||
|
mozallowfullscreen="true"
|
||||||
></iframe>
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -11,6 +11,10 @@ const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
require: false,
|
require: false,
|
||||||
type: String
|
type: String
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
require: false,
|
||||||
|
type: Boolean
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const emit = defineEmits<{ (e: 'update:modelValue', v: string) }>()
|
const emit = defineEmits<{ (e: 'update:modelValue', v: string) }>()
|
||||||
|
@ -92,6 +96,12 @@ function onCurrentChange(page) {
|
||||||
currentPage.value = page
|
currentPage.value = page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearIcon() {
|
||||||
|
icon.value = ''
|
||||||
|
emit('update:modelValue', '')
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => {
|
() => {
|
||||||
return props.modelValue
|
return props.modelValue
|
||||||
|
@ -115,14 +125,14 @@ watch(
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="selector">
|
<div class="selector">
|
||||||
<ElInput v-model="inputValue" @click="visible = !visible">
|
<ElInput v-model="inputValue" @click="visible = !visible" :clearable="props.clearable" @clear="clearIcon">
|
||||||
<template #append>
|
<template #append>
|
||||||
<ElPopover
|
<ElPopover
|
||||||
:popper-options="{
|
:popper-options="{
|
||||||
placement: 'auto'
|
placement: 'auto'
|
||||||
}"
|
}"
|
||||||
:visible="visible"
|
:visible="visible"
|
||||||
:width="350"
|
:width="355"
|
||||||
popper-class="pure-popper"
|
popper-class="pure-popper"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
>
|
>
|
||||||
|
@ -147,7 +157,7 @@ watch(
|
||||||
>
|
>
|
||||||
<ElDivider border-style="dashed" class="tab-divider" />
|
<ElDivider border-style="dashed" class="tab-divider" />
|
||||||
<ElScrollbar height="220px">
|
<ElScrollbar height="220px">
|
||||||
<ul class="ml-2 flex flex-wrap px-2">
|
<ul class="ml-2 flex flex-wrap">
|
||||||
<li
|
<li
|
||||||
v-for="(item, key) in pageList"
|
v-for="(item, key) in pageList"
|
||||||
:key="key"
|
:key="key"
|
||||||
|
@ -171,7 +181,7 @@ watch(
|
||||||
background
|
background
|
||||||
class="h-10 flex items-center justify-center"
|
class="h-10 flex items-center justify-center"
|
||||||
layout="prev, pager, next"
|
layout="prev, pager, next"
|
||||||
small
|
size="small"
|
||||||
@current-change="onCurrentChange"
|
@current-change="onCurrentChange"
|
||||||
/>
|
/>
|
||||||
</ElPopover>
|
</ElPopover>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<el-radio-group v-model="shortcutDays" @change="handleShortcutDaysChange">
|
<el-radio-group v-model="shortcutDays" @change="handleShortcutDaysChange">
|
||||||
<el-radio-button :label="1">昨天</el-radio-button>
|
<el-radio-button :value="1">昨天</el-radio-button>
|
||||||
<el-radio-button :label="7">最近7天</el-radio-button>
|
<el-radio-button :value="7">最近7天</el-radio-button>
|
||||||
<el-radio-button :label="30">最近30天</el-radio-button>
|
<el-radio-button :value="30">最近30天</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="times"
|
v-model="times"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="upload-file">
|
<div v-if="!disabled" class="upload-file">
|
||||||
<el-upload
|
<el-upload
|
||||||
ref="uploadRef"
|
ref="uploadRef"
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
|
@ -20,11 +20,11 @@
|
||||||
class="upload-file-uploader"
|
class="upload-file-uploader"
|
||||||
name="file"
|
name="file"
|
||||||
>
|
>
|
||||||
<el-button v-if="!disabled" type="primary">
|
<el-button type="primary">
|
||||||
<Icon icon="ep:upload-filled" />
|
<Icon icon="ep:upload-filled" />
|
||||||
选取文件
|
选取文件
|
||||||
</el-button>
|
</el-button>
|
||||||
<template v-if="isShowTip && !disabled" #tip>
|
<template v-if="isShowTip" #tip>
|
||||||
<div style="font-size: 8px">
|
<div style="font-size: 8px">
|
||||||
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
|
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,7 +32,6 @@
|
||||||
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> 的文件
|
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> 的文件
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- TODO @puhui999:1)表单展示的时候,位置会偏掉,已发微信;2)disable 的时候,应该把【删除】按钮也隐藏掉? -->
|
|
||||||
<template #file="row">
|
<template #file="row">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span>{{ row.file.name }}</span>
|
<span>{{ row.file.name }}</span>
|
||||||
|
@ -54,6 +53,18 @@
|
||||||
</template>
|
</template>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 上传操作禁用时 -->
|
||||||
|
<div v-if="disabled" class="upload-file">
|
||||||
|
<div v-for="(file, index) in fileList" :key="index" class="flex items-center file-list-item">
|
||||||
|
<span>{{ file.name }}</span>
|
||||||
|
<div class="ml-10px">
|
||||||
|
<el-link :href="file.url" :underline="false" download target="_blank" type="primary">
|
||||||
|
下载
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
@ -211,4 +222,9 @@ const emitUpdateModelValue = () => {
|
||||||
:deep(.ele-upload-list__item-content-action .el-link) {
|
:deep(.ele-upload-list__item-content-action .el-link) {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-list-item {
|
||||||
|
border: 1px dashed var(--el-border-color-darker);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -45,17 +45,20 @@
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="loopInstanceForm.asyncBefore"
|
v-model="loopInstanceForm.asyncBefore"
|
||||||
label="异步前"
|
label="异步前"
|
||||||
|
value="异步前"
|
||||||
@change="updateLoopAsync('asyncBefore')"
|
@change="updateLoopAsync('asyncBefore')"
|
||||||
/>
|
/>
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="loopInstanceForm.asyncAfter"
|
v-model="loopInstanceForm.asyncAfter"
|
||||||
label="异步后"
|
label="异步后"
|
||||||
|
value="异步后"
|
||||||
@change="updateLoopAsync('asyncAfter')"
|
@change="updateLoopAsync('asyncAfter')"
|
||||||
/>
|
/>
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="loopInstanceForm.exclusive"
|
v-model="loopInstanceForm.exclusive"
|
||||||
v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
|
v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
|
||||||
label="排除"
|
label="排除"
|
||||||
|
value="排除"
|
||||||
@change="updateLoopAsync('exclusive')"
|
@change="updateLoopAsync('exclusive')"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
|
@ -6,13 +6,20 @@
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="taskConfigForm.asyncBefore"
|
v-model="taskConfigForm.asyncBefore"
|
||||||
label="异步前"
|
label="异步前"
|
||||||
|
value="异步前"
|
||||||
|
@change="changeTaskAsync"
|
||||||
|
/>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="taskConfigForm.asyncAfter"
|
||||||
|
label="异步后"
|
||||||
|
value="异步后"
|
||||||
@change="changeTaskAsync"
|
@change="changeTaskAsync"
|
||||||
/>
|
/>
|
||||||
<el-checkbox v-model="taskConfigForm.asyncAfter" label="异步后" @change="changeTaskAsync" />
|
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="taskConfigForm.exclusive"
|
v-model="taskConfigForm.exclusive"
|
||||||
v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore"
|
v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore"
|
||||||
label="排除"
|
label="排除"
|
||||||
|
value="排除"
|
||||||
@change="changeTaskAsync"
|
@change="changeTaskAsync"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
|
@ -206,15 +206,12 @@ const refreshToken = async () => {
|
||||||
const handleAuthorized = () => {
|
const handleAuthorized = () => {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
if (!isRelogin.show) {
|
if (!isRelogin.show) {
|
||||||
// 如果已经到重新登录页面则不进行弹窗提示
|
|
||||||
if (window.location.href.includes('login?redirect=')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isRelogin.show = true
|
isRelogin.show = true
|
||||||
ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
|
ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
|
||||||
showCancelButton: false,
|
showCancelButton: false,
|
||||||
closeOnClickModal: false,
|
closeOnClickModal: false,
|
||||||
showClose: false,
|
showClose: false,
|
||||||
|
closeOnPressEscape: false,
|
||||||
confirmButtonText: t('login.relogin'),
|
confirmButtonText: t('login.relogin'),
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|
|
@ -71,8 +71,5 @@ $prefix-cls: #{$namespace}-layout;
|
||||||
|
|
||||||
.#{$prefix-cls} {
|
.#{$prefix-cls} {
|
||||||
background-color: var(--app-content-bg-color);
|
background-color: var(--app-content-bg-color);
|
||||||
:deep(.#{$elNamespace}-scrollbar__view) {
|
|
||||||
height: 99% !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -36,27 +36,10 @@ provide('reload', reload)
|
||||||
<template>
|
<template>
|
||||||
<section
|
<section
|
||||||
:class="[
|
:class="[
|
||||||
'p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]',
|
'p-[var(--app-content-padding)] w-full bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]',
|
||||||
{
|
{
|
||||||
'!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':
|
'!min-h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))] pb-0':
|
||||||
(fixedHeader &&
|
footer
|
||||||
(layout === 'classic' || layout === 'topLeft' || layout === 'top') &&
|
|
||||||
footer) ||
|
|
||||||
(!tagsView && layout === 'top' && footer),
|
|
||||||
'!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height))]':
|
|
||||||
tagsView && layout === 'top' && footer,
|
|
||||||
|
|
||||||
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--top-tool-height)-var(--app-footer-height))]':
|
|
||||||
!fixedHeader && layout === 'classic' && footer,
|
|
||||||
|
|
||||||
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':
|
|
||||||
!fixedHeader && layout === 'topLeft' && footer,
|
|
||||||
|
|
||||||
'!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding))]':
|
|
||||||
fixedHeader && layout === 'cutMenu' && footer,
|
|
||||||
|
|
||||||
'!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding)-var(--tags-view-height))]':
|
|
||||||
!fixedHeader && layout === 'cutMenu' && footer
|
|
||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
|
|
@ -17,7 +17,7 @@ const title = computed(() => appStore.getTitle)
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="prefixCls"
|
:class="prefixCls"
|
||||||
class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)]"
|
class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)] overflow-hidden"
|
||||||
>
|
>
|
||||||
<span class="text-14px">Copyright ©2022-{{ title }}</span>
|
<span class="text-14px">Copyright ©2022-{{ title }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -195,6 +195,16 @@ $prefix-cls: #{$namespace}-menu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 垂直菜单
|
||||||
|
&__vertical {
|
||||||
|
:deep(.#{$elNamespace}-menu--vertical) {
|
||||||
|
&:not(.#{$elNamespace}-menu--collapse) .#{$elNamespace}-sub-menu__title,
|
||||||
|
.#{$elNamespace}-menu-item {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 水平菜单
|
// 水平菜单
|
||||||
&__horizontal {
|
&__horizontal {
|
||||||
height: calc(var(--top-tool-height)) !important;
|
height: calc(var(--top-tool-height)) !important;
|
||||||
|
|
|
@ -126,8 +126,10 @@ const copyConfig = async () => {
|
||||||
message: ${appStore.getMessage},
|
message: ${appStore.getMessage},
|
||||||
// 标签页
|
// 标签页
|
||||||
tagsView: ${appStore.getTagsView},
|
tagsView: ${appStore.getTagsView},
|
||||||
|
// 标签页
|
||||||
|
tagsViewImmerse: ${appStore.getTagsViewImmerse},
|
||||||
// 标签页图标
|
// 标签页图标
|
||||||
getTagsViewIcon: ${appStore.getTagsViewIcon},
|
tagsViewIcon: ${appStore.getTagsViewIcon},
|
||||||
// logo
|
// logo
|
||||||
logo: ${appStore.getLogo},
|
logo: ${appStore.getLogo},
|
||||||
// 菜单手风琴
|
// 菜单手风琴
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -139,7 +139,7 @@ export default defineComponent({
|
||||||
id={`${variables.namespace}-menu`}
|
id={`${variables.namespace}-menu`}
|
||||||
class={[
|
class={[
|
||||||
prefixCls,
|
prefixCls,
|
||||||
'relative bg-[var(--left-menu-bg-color)] top-1px layout-border__right',
|
'relative bg-[var(--left-menu-bg-color)] layout-border__right',
|
||||||
{
|
{
|
||||||
'w-[var(--tab-menu-max-width)]': !unref(collapse),
|
'w-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||||
'w-[var(--tab-menu-min-width)]': unref(collapse)
|
'w-[var(--tab-menu-min-width)]': unref(collapse)
|
||||||
|
@ -147,7 +147,7 @@ export default defineComponent({
|
||||||
]}
|
]}
|
||||||
onMouseleave={mouseleave}
|
onMouseleave={mouseleave}
|
||||||
>
|
>
|
||||||
<ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height)-1px)]">
|
<ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height))]">
|
||||||
<div>
|
<div>
|
||||||
{() => {
|
{() => {
|
||||||
return unref(tabRouters).map((v) => {
|
return unref(tabRouters).map((v) => {
|
||||||
|
@ -199,7 +199,7 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
'!left-[var(--tab-menu-min-width)]': unref(collapse),
|
'!left-[var(--tab-menu-min-width)]': unref(collapse),
|
||||||
'!left-[var(--tab-menu-max-width)]': !unref(collapse),
|
'!left-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||||
'!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),
|
'!w-[var(--left-menu-max-width)]': unref(showMenu) || unref(fixedMenu),
|
||||||
'!w-0': !unref(showMenu) && !unref(fixedMenu)
|
'!w-0': !unref(showMenu) && !unref(fixedMenu)
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -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-[var(--tags-view-height)] 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>
|
||||||
|
@ -485,10 +491,10 @@ $prefix-cls: #{$namespace}-tags-view;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 1px);
|
height: 100%;
|
||||||
border-left: 1px solid var(--el-border-color);
|
border-left: 1px solid var(--el-border-color);
|
||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
@ -496,10 +502,10 @@ $prefix-cls: #{$namespace}-tags-view;
|
||||||
&--first {
|
&--first {
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 1px);
|
height: 100%;
|
||||||
border-right: 1px solid var(--el-border-color);
|
border-right: 1px solid var(--el-border-color);
|
||||||
border-left: none;
|
border-left: none;
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -509,14 +515,15 @@ $prefix-cls: #{$namespace}-tags-view;
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 3px;
|
||||||
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;
|
||||||
border: 1px solid #d9d9d9;
|
border: 1px solid #d9d9d9;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
&--close {
|
&--close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -525,6 +532,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 +540,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 +554,48 @@ $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: 2px;
|
||||||
|
height: calc(100% - 3px);
|
||||||
|
padding-right: 35px;
|
||||||
|
margin: 0 -10px;
|
||||||
|
border: none !important;
|
||||||
|
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='68' height='34' viewBox='0 0 68 34' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m27,0c-7.99582,0 -11.95105,0.00205 -12,12l0,6c0,8.284 -0.48549,16.49691 -8.76949,16.49691l54.37857,-0.11145c-8.284,0 -8.60908,-8.10146 -8.60908,-16.38546l0,-6c0.11145,-12.08445 -4.38441,-12 -12,-12l-13,0z' fill='%23409eff'/%3E%3C/svg%3E")
|
||||||
|
12 27 15;
|
||||||
|
|
||||||
|
.#{$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,12 +622,19 @@ $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:not(.is-active) {
|
||||||
|
&:hover {
|
||||||
|
color: var(--el-color-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -23,7 +23,7 @@ const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
const prefixCls = getPrefixCls('user-info')
|
const prefixCls = getPrefixCls('user-info')
|
||||||
|
|
||||||
const avatar = computed(() => userStore.user.avatar ?? avatarImg)
|
const avatar = computed(() => userStore.user.avatar || avatarImg)
|
||||||
const userName = computed(() => userStore.user.nickname ?? 'Admin')
|
const userName = computed(() => userStore.user.nickname ?? 'Admin')
|
||||||
|
|
||||||
// 锁定屏幕
|
// 锁定屏幕
|
||||||
|
|
|
@ -21,7 +21,7 @@ const props = defineProps({
|
||||||
})
|
})
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const avatar = computed(() => userStore.user.avatar ?? avatarImg)
|
const avatar = computed(() => userStore.user.avatar || avatarImg)
|
||||||
const userName = computed(() => userStore.user.nickname ?? 'Admin')
|
const userName = computed(() => userStore.user.nickname ?? 'Admin')
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
|
@ -22,7 +22,7 @@ const showDate = ref(true)
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
const prefixCls = getPrefixCls('lock-page')
|
const prefixCls = getPrefixCls('lock-page')
|
||||||
|
|
||||||
const avatar = computed(() => userStore.user.avatar ?? avatarImg)
|
const avatar = computed(() => userStore.user.avatar || avatarImg)
|
||||||
const userName = computed(() => userStore.user.nickname ?? 'Admin')
|
const userName = computed(() => userStore.user.nickname ?? 'Admin')
|
||||||
|
|
||||||
const lockStore = useLockStore()
|
const lockStore = useLockStore()
|
||||||
|
|
|
@ -126,7 +126,7 @@ export const useRenderLayout = () => {
|
||||||
|
|
||||||
<ToolHeader class="flex-1"></ToolHeader>
|
<ToolHeader class="flex-1"></ToolHeader>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-0 top-[var(--logo-height)+1px] h-[calc(100%-1px-var(--logo-height))] w-full flex">
|
<div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-full flex">
|
||||||
<Menu class="relative layout-border__right !h-full"></Menu>
|
<Menu class="relative layout-border__right !h-full"></Menu>
|
||||||
<div
|
<div
|
||||||
class={[
|
class={[
|
||||||
|
@ -157,9 +157,9 @@ export const useRenderLayout = () => {
|
||||||
'layout-border__bottom absolute',
|
'layout-border__bottom absolute',
|
||||||
{
|
{
|
||||||
'!fixed top-0 left-0 z-10': fixedHeader.value,
|
'!fixed top-0 left-0 z-10': fixedHeader.value,
|
||||||
'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)] mt-[calc(var(--logo-height)+1px)]':
|
'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)] mt-[var(--logo-height)]':
|
||||||
collapse.value && fixedHeader.value,
|
collapse.value && fixedHeader.value,
|
||||||
'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)] mt-[calc(var(--logo-height)+1px)]':
|
'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)] mt-[var(--logo-height)]':
|
||||||
!collapse.value && fixedHeader.value
|
!collapse.value && fixedHeader.value
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
@ -190,24 +190,14 @@ export const useRenderLayout = () => {
|
||||||
<Menu class="h-[var(--top-tool-height)] flex-1 px-10px"></Menu>
|
<Menu class="h-[var(--top-tool-height)] flex-1 px-10px"></Menu>
|
||||||
<ToolHeader></ToolHeader>
|
<ToolHeader></ToolHeader>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class={[`${prefixCls}-content`, 'w-full h-[calc(100%-var(--top-tool-height))]']}>
|
||||||
class={[
|
|
||||||
`${prefixCls}-content`,
|
|
||||||
'w-full',
|
|
||||||
{
|
|
||||||
'h-[calc(100%-var(--app-footer-height))]': !fixedHeader.value,
|
|
||||||
'h-[calc(100%-var(--tags-view-height)-var(--app-footer-height))]': fixedHeader.value
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<ElScrollbar
|
<ElScrollbar
|
||||||
v-loading={pageLoading.value}
|
v-loading={pageLoading.value}
|
||||||
class={[
|
class={[
|
||||||
`${prefixCls}-content-scrollbar`,
|
`${prefixCls}-content-scrollbar`,
|
||||||
{
|
{
|
||||||
'mt-[var(--tags-view-height)] !pb-[calc(var(--tags-view-height)+var(--app-footer-height))]':
|
'!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':
|
||||||
fixedHeader.value,
|
fixedHeader.value
|
||||||
'pb-[var(--app-footer-height)]': !fixedHeader.value
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -216,7 +206,7 @@ export const useRenderLayout = () => {
|
||||||
class={[
|
class={[
|
||||||
'layout-border__bottom layout-border__top relative',
|
'layout-border__bottom layout-border__top relative',
|
||||||
{
|
{
|
||||||
'!fixed w-full top-[calc(var(--top-tool-height)+1px)] left-0': fixedHeader.value
|
'!fixed w-full top-[var(--top-tool-height)] left-0': fixedHeader.value
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
style="transition: width var(--transition-time-02), left var(--transition-time-02);"
|
style="transition: width var(--transition-time-02), left var(--transition-time-02);"
|
||||||
|
@ -238,7 +228,7 @@ export const useRenderLayout = () => {
|
||||||
|
|
||||||
<ToolHeader class="flex-1"></ToolHeader>
|
<ToolHeader class="flex-1"></ToolHeader>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-[calc(100%-2px)] flex">
|
<div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-full flex">
|
||||||
<TabMenu></TabMenu>
|
<TabMenu></TabMenu>
|
||||||
<div
|
<div
|
||||||
class={[
|
class={[
|
||||||
|
@ -270,18 +260,16 @@ export const useRenderLayout = () => {
|
||||||
{tagsView.value ? (
|
{tagsView.value ? (
|
||||||
<TagsView
|
<TagsView
|
||||||
class={[
|
class={[
|
||||||
'relative layout-border__bottom layout-border__top',
|
'relative layout-border__bottom',
|
||||||
{
|
{
|
||||||
'!fixed top-0 left-0 z-10': fixedHeader.value,
|
'!fixed top-0 left-0 z-10': fixedHeader.value,
|
||||||
'w-[calc(100%-var(--tab-menu-min-width))] !left-[var(--tab-menu-min-width)] mt-[var(--logo-height)]':
|
'w-[calc(100%-var(--tab-menu-min-width))] !left-[var(--tab-menu-min-width)] mt-[var(--logo-height)]':
|
||||||
collapse.value && fixedHeader.value,
|
collapse.value && fixedHeader.value && !fixedMenu.value,
|
||||||
'w-[calc(100%-var(--tab-menu-max-width))] !left-[var(--tab-menu-max-width)] mt-[var(--logo-height)]':
|
'w-[calc(100%-var(--tab-menu-max-width))] !left-[var(--tab-menu-max-width)] mt-[var(--logo-height)]':
|
||||||
!collapse.value && fixedHeader.value,
|
!collapse.value && fixedHeader.value && !fixedMenu.value,
|
||||||
'!fixed top-0 !left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] z-10':
|
'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] !left-[calc(var(--tab-menu-min-width)+var(--left-menu-max-width))] mt-[var(--logo-height)]':
|
||||||
fixedHeader.value && fixedMenu.value,
|
|
||||||
'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] !left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
|
|
||||||
collapse.value && fixedHeader.value && fixedMenu.value,
|
collapse.value && fixedHeader.value && fixedMenu.value,
|
||||||
'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] !left-[var(--tab-menu-max-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
|
'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] !left-[calc(var(--tab-menu-max-width)+var(--left-menu-max-width))] mt-[var(--logo-height)]':
|
||||||
!collapse.value && fixedHeader.value && fixedMenu.value
|
!collapse.value && fixedHeader.value && fixedMenu.value
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -92,6 +92,7 @@ export default {
|
||||||
localeIcon: '多语言图标',
|
localeIcon: '多语言图标',
|
||||||
messageIcon: '消息图标',
|
messageIcon: '消息图标',
|
||||||
tagsView: '标签页',
|
tagsView: '标签页',
|
||||||
|
tagsViewImmerse: '标签页沉浸',
|
||||||
logo: '标志',
|
logo: '标志',
|
||||||
greyMode: '灰色模式',
|
greyMode: '灰色模式',
|
||||||
fixedHeader: '固定头部',
|
fixedHeader: '固定头部',
|
||||||
|
|
|
@ -593,6 +593,16 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
component: () => import('@/views/Error/404.vue'),
|
||||||
|
name: '',
|
||||||
|
meta: {
|
||||||
|
title: '404',
|
||||||
|
hidden: true,
|
||||||
|
breadcrumb: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
@ -40,10 +40,12 @@ export const usePermissionStore = defineStore('permission', {
|
||||||
}
|
}
|
||||||
const routerMap: AppRouteRecordRaw[] = generateRoute(res)
|
const routerMap: AppRouteRecordRaw[] = generateRoute(res)
|
||||||
// 动态路由,404一定要放到最后面
|
// 动态路由,404一定要放到最后面
|
||||||
|
// preschooler:vue-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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -64,3 +64,11 @@ body {
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
:after,
|
||||||
|
:before {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
|
@ -56,11 +56,11 @@ export const defaultShortcuts = [
|
||||||
* 时间日期转换
|
* 时间日期转换
|
||||||
* @param date 当前时间,new Date() 格式
|
* @param date 当前时间,new Date() 格式
|
||||||
* @param format 需要转换的时间格式字符串
|
* @param format 需要转换的时间格式字符串
|
||||||
* @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
|
* @description format 字符串随意,如 `YYYY-MM、YYYY-MM-DD`
|
||||||
* @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
|
* @description format 季度:"YYYY-MM-DD HH:mm:ss QQQQ"
|
||||||
* @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
|
* @description format 星期:"YYYY-MM-DD HH:mm:ss WWW"
|
||||||
* @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
|
* @description format 几周:"YYYY-MM-DD HH:mm:ss ZZZ"
|
||||||
* @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
|
* @description format 季度 + 星期 + 几周:"YYYY-MM-DD HH:mm:ss WWW QQQQ ZZZ"
|
||||||
* @returns 返回拼接后的时间字符串
|
* @returns 返回拼接后的时间字符串
|
||||||
*/
|
*/
|
||||||
export function formatDate(date: Date, format?: string): string {
|
export function formatDate(date: Date, format?: string): string {
|
||||||
|
@ -110,7 +110,7 @@ export function getWeek(dateTime: Date): number {
|
||||||
* @description param 3天: 60 * 60* 24 * 1000 * 3
|
* @description param 3天: 60 * 60* 24 * 1000 * 3
|
||||||
* @returns 返回拼接后的时间字符串
|
* @returns 返回拼接后的时间字符串
|
||||||
*/
|
*/
|
||||||
export function formatPast(param: string | Date, format = 'YYYY-mm-dd HH:MM:SS'): string {
|
export function formatPast(param: string | Date, format = 'YYYY-MM-DD HH:mm:ss'): string {
|
||||||
// 传入格式处理、存储转换值
|
// 传入格式处理、存储转换值
|
||||||
let t: any, s: number
|
let t: any, s: number
|
||||||
// 获取js 时间戳
|
// 获取js 时间戳
|
||||||
|
|
|
@ -18,8 +18,8 @@ export const isObject = (val: any): val is Record<any, any> => {
|
||||||
return val !== null && is(val, 'Object')
|
return val !== null && is(val, 'Object')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isEmpty = <T = unknown>(val: T): val is T => {
|
export const isEmpty = (val: any): boolean => {
|
||||||
if (val === null) {
|
if (val === null || val === undefined || typeof val === 'undefined') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (isArray(val) || isString(val)) {
|
if (isArray(val) || isString(val)) {
|
||||||
|
|
|
@ -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,24 +27,27 @@
|
||||||
</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 at-2xl:justify-end at-xl:justify-end"
|
||||||
|
style="color: var(--el-text-color-primary);"
|
||||||
>
|
>
|
||||||
<div class="flex items-center at-2xl:hidden at-xl:hidden">
|
<div class="flex items-center at-2xl:hidden at-xl:hidden">
|
||||||
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
|
<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 />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右边的登录界面 -->
|
<!-- 右边的登录界面 -->
|
||||||
<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)" />
|
||||||
|
@ -102,3 +105,15 @@ $prefix-cls: #{$namespace}-login;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.dark .login-form {
|
||||||
|
.el-divider__text {
|
||||||
|
background-color: var(--login-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
background-color: var(--login-bg-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-for="scope in queryParams.scopes"
|
v-for="scope in queryParams.scopes"
|
||||||
:key="scope"
|
:key="scope"
|
||||||
:label="scope"
|
:value="scope"
|
||||||
style="display: block; margin-bottom: -10px"
|
style="display: block; margin-bottom: -10px"
|
||||||
>
|
>
|
||||||
{{ formatScope(scope) }}
|
{{ formatScope(scope) }}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
<Form ref="formRef" :labelWidth="200" :rules="rules" :schema="schema">
|
<Form ref="formRef" :labelWidth="200" :rules="rules" :schema="schema">
|
||||||
<template #sex="form">
|
<template #sex="form">
|
||||||
<el-radio-group v-model="form['sex']">
|
<el-radio-group v-model="form['sex']">
|
||||||
<el-radio :label="1">{{ t('profile.user.man') }}</el-radio>
|
<el-radio :value="1">{{ t('profile.user.man') }}</el-radio>
|
||||||
<el-radio :label="2">{{ t('profile.user.woman') }}</el-radio>
|
<el-radio :value="2">{{ t('profile.user.woman') }}</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</template>
|
</template>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -27,7 +27,7 @@ defineOptions({ name: 'BasicInfo' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
// 表单校验
|
// 表单校验
|
||||||
const rules = reactive<FormRules>({
|
const rules = reactive<FormRules>({
|
||||||
nickname: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }],
|
nickname: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }],
|
||||||
|
|
|
@ -78,7 +78,7 @@ const userStore = useUserStore()
|
||||||
const messageContainer: any = ref(null)
|
const messageContainer: any = ref(null)
|
||||||
const isScrolling = ref(false) //用于判断用户是否在滚动
|
const isScrolling = ref(false) //用于判断用户是否在滚动
|
||||||
|
|
||||||
const userAvatar = computed(() => userStore.user.avatar ?? userAvatarDefaultImg)
|
const userAvatar = computed(() => userStore.user.avatar || userAvatarDefaultImg)
|
||||||
const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg)
|
const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg)
|
||||||
|
|
||||||
// 定义 props
|
// 定义 props
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
end-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-220px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
|
|
|
@ -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'
|
||||||
|
@ -137,6 +137,7 @@ defineExpose({
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-card {
|
.my-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -149,13 +150,16 @@ defineExpose({
|
||||||
@extend .hide-scroll-bar;
|
@extend .hide-scroll-bar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// markmap的tool样式覆盖
|
// markmap的tool样式覆盖
|
||||||
: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;
|
||||||
|
|
|
@ -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 @@
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
end-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-220px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.value"
|
:value="dict.value"
|
||||||
>
|
>
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.value"
|
:value="dict.value"
|
||||||
>
|
>
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.value"
|
:value="dict.value"
|
||||||
>
|
>
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.value"
|
:value="dict.value"
|
||||||
>
|
>
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
@ -69,7 +69,7 @@ import { getIntDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { ChatRoleApi, ChatRoleVO } from '@/api/ai/model/chatRole'
|
import { ChatRoleApi, ChatRoleVO } from '@/api/ai/model/chatRole'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
import { ChatModelApi, ChatModelVO } from '@/api/ai/model/chatModel'
|
import { ChatModelApi, ChatModelVO } from '@/api/ai/model/chatModel'
|
||||||
import {FormRules} from "element-plus";
|
import { FormRules } from 'element-plus'
|
||||||
|
|
||||||
/** AI 聊天角色 表单 */
|
/** AI 聊天角色 表单 */
|
||||||
defineOptions({ name: 'ChatRoleForm' })
|
defineOptions({ name: 'ChatRoleForm' })
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<ContentWrap class="w-300px h-full mb-[0!important]">
|
<ContentWrap class="w-300px h-full mb-[0!important]">
|
||||||
<el-radio-group v-model="generateMode" class="mb-15px">
|
<el-radio-group v-model="generateMode" class="mb-15px">
|
||||||
<el-radio-button label="desc">
|
<el-radio-button value="desc"> 描述模式 </el-radio-button>
|
||||||
描述模式
|
<el-radio-button value="lyric"> 歌词模式 </el-radio-button>
|
||||||
</el-radio-button>
|
|
||||||
<el-radio-button label="lyric">
|
|
||||||
歌词模式
|
|
||||||
</el-radio-button>
|
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
|
||||||
<!-- 描述模式/歌词模式 切换 -->
|
<!-- 描述模式/歌词模式 切换 -->
|
||||||
<component :is="generateMode === 'desc' ? desc : lyric" ref="modeRef"/>
|
<component :is="generateMode === 'desc' ? desc : lyric" ref="modeRef" />
|
||||||
|
|
||||||
<el-button type="primary" round class="w-full" @click="generateMusic">
|
<el-button type="primary" round class="w-full" @click="generateMusic"> 创作音乐 </el-button>
|
||||||
创作音乐
|
|
||||||
</el-button>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -34,8 +28,8 @@ const modeRef = ref<Nullable<{ formData: Recordable }>>(null)
|
||||||
*@Description: 根据信息生成音乐
|
*@Description: 根据信息生成音乐
|
||||||
*@MethodAuthor: xiaohong
|
*@MethodAuthor: xiaohong
|
||||||
*@Date: 2024-06-27 16:40:16
|
*@Date: 2024-06-27 16:40:16
|
||||||
*/
|
*/
|
||||||
function generateMusic () {
|
function generateMusic() {
|
||||||
emits('generate-music', {formData: unref(modeRef)?.formData})
|
emits('generate-music', { formData: unref(modeRef)?.formData })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
end-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-220px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="是否发布" prop="publicStatus">
|
<el-form-item label="是否发布" prop="publicStatus">
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.value"
|
:value="dict.value"
|
||||||
>
|
>
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.value"
|
:value="dict.value"
|
||||||
>
|
>
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.value"
|
:value="dict.value"
|
||||||
>
|
>
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.value"
|
:value="dict.value"
|
||||||
>
|
>
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
|
|
@ -0,0 +1,311 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="h-50px position-fixed bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
|
||||||
|
>
|
||||||
|
<el-popover :visible="passVisible" placement="top-end" :width="500" trigger="click">
|
||||||
|
<template #reference>
|
||||||
|
<el-button plain type="success" @click="openPopover('1')">
|
||||||
|
<Icon icon="ep:select" /> 通过
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
|
||||||
|
<el-form
|
||||||
|
label-position="top"
|
||||||
|
class="mb-auto"
|
||||||
|
ref="formRef"
|
||||||
|
:model="auditForm"
|
||||||
|
:rules="auditRule"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人">
|
||||||
|
{{ processInstance?.startUser.nickname }}
|
||||||
|
<el-tag size="small" type="info" class="ml-8px">
|
||||||
|
{{ processInstance?.startUser.deptName }}
|
||||||
|
</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-card v-if="runningTask.formId > 0" class="mb-15px !-mt-10px">
|
||||||
|
<template #header>
|
||||||
|
<span class="el-icon-picture-outline"> 填写表单【{{ runningTask?.formName }}】 </span>
|
||||||
|
</template>
|
||||||
|
<form-create
|
||||||
|
v-model="approveForm.value"
|
||||||
|
v-model:api="approveFormFApi"
|
||||||
|
:option="approveForm.option"
|
||||||
|
:rule="approveForm.rule"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
<el-form-item label="审批建议" prop="reason">
|
||||||
|
<el-input v-model="auditForm.reason" placeholder="请输入审批建议" type="textarea" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="抄送人" prop="copyUserIds">
|
||||||
|
<el-select v-model="auditForm.copyUserIds" multiple placeholder="请选择抄送人">
|
||||||
|
<el-option
|
||||||
|
v-for="itemx in userOptions"
|
||||||
|
:key="itemx.id"
|
||||||
|
:label="itemx.nickname"
|
||||||
|
:value="itemx.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button :disabled="formLoading" type="success" @click="handleAudit(true)">
|
||||||
|
通过
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="passVisible = false"> 取消 </el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
|
<el-popover :visible="rejectVisible" placement="top-end" :width="500" trigger="click">
|
||||||
|
<template #reference>
|
||||||
|
<el-button class="mr-20px" plain type="danger" @click="openPopover('2')">
|
||||||
|
<Icon icon="ep:close" /> 拒绝
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
|
||||||
|
<el-form
|
||||||
|
label-position="top"
|
||||||
|
class="mb-auto"
|
||||||
|
ref="formRef"
|
||||||
|
:model="auditForm"
|
||||||
|
:rules="auditRule"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人">
|
||||||
|
{{ processInstance?.startUser.nickname }}
|
||||||
|
<el-tag size="small" type="info" class="ml-8px">
|
||||||
|
{{ processInstance?.startUser.deptName }}
|
||||||
|
</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-card v-if="runningTask.formId > 0" class="mb-15px !-mt-10px">
|
||||||
|
<template #header>
|
||||||
|
<span class="el-icon-picture-outline"> 填写表单【{{ runningTask?.formName }}】 </span>
|
||||||
|
</template>
|
||||||
|
<form-create
|
||||||
|
v-model="approveForm.value"
|
||||||
|
v-model:api="approveFormFApi"
|
||||||
|
:option="approveForm.option"
|
||||||
|
:rule="approveForm.rule"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
<el-form-item label="审批建议" prop="reason">
|
||||||
|
<el-input v-model="auditForm.reason" placeholder="请输入审批建议" type="textarea" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="抄送人" prop="copyUserIds">
|
||||||
|
<el-select v-model="auditForm.copyUserIds" multiple placeholder="请选择抄送人">
|
||||||
|
<el-option
|
||||||
|
v-for="itemx in userOptions"
|
||||||
|
:key="itemx.id"
|
||||||
|
:label="itemx.nickname"
|
||||||
|
:value="itemx.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button :disabled="formLoading" type="danger" @click="handleAudit(false)">
|
||||||
|
拒绝
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="rejectVisible = false"> 取消 </el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
|
<div @click="handleSend"> <Icon :size="14" icon="svg-icon:send" /> 抄送 </div>
|
||||||
|
<div @click="openTaskUpdateAssigneeForm">
|
||||||
|
<Icon :size="14" icon="fa:share-square-o" /> 转交
|
||||||
|
</div>
|
||||||
|
<div @click="handleDelegate"> <Icon :size="14" icon="ep:position" /> 委派 </div>
|
||||||
|
<div @click="handleSign"> <Icon :size="14" icon="ep:plus" /> 加签 </div>
|
||||||
|
<div @click="handleBack"> <Icon :size="14" icon="fa:mail-reply" /> 退回 </div>
|
||||||
|
</div>
|
||||||
|
<!-- 弹窗:转派审批人 -->
|
||||||
|
<TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
|
||||||
|
<!-- 弹窗:回退节点 -->
|
||||||
|
<TaskReturnForm ref="taskReturnFormRef" @success="getDetail" />
|
||||||
|
<!-- 弹窗:委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
|
||||||
|
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
|
||||||
|
<!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
|
||||||
|
<TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import * as TaskApi from '@/api/bpm/task'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import TaskReturnForm from './dialog/TaskReturnForm.vue'
|
||||||
|
import TaskDelegateForm from './dialog/TaskDelegateForm.vue'
|
||||||
|
import TaskTransferForm from './dialog/TaskTransferForm.vue'
|
||||||
|
import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
|
||||||
|
defineOptions({ name: 'ProcessInstanceBtnConatiner' })
|
||||||
|
|
||||||
|
const userId = useUserStore().getUser.id // 当前登录的编号
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { proxy } = getCurrentInstance() as any
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
defineProps({
|
||||||
|
processInstance: propTypes.any, // 流程实例信息
|
||||||
|
userOptions: propTypes.any
|
||||||
|
})
|
||||||
|
const formLoading = ref(false) // 表单加载中
|
||||||
|
const passVisible = ref(false) // 是否显示
|
||||||
|
const rejectVisible = ref(false) // 是否显示
|
||||||
|
// ========== 审批信息 ==========
|
||||||
|
const runningTask = ref<any>({}) // 运行中的任务
|
||||||
|
const auditForm = ref<any>({}) // 审批任务的表单
|
||||||
|
const approveForm = ref<any>({}) // 审批通过时,额外的补充信息
|
||||||
|
const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
|
||||||
|
const formRef = ref()
|
||||||
|
const auditRule = reactive({
|
||||||
|
reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
|
||||||
|
watch(
|
||||||
|
() => approveFormFApi.value,
|
||||||
|
(val) => {
|
||||||
|
val?.btn?.show(false)
|
||||||
|
val?.resetBtn?.show(false)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
/**
|
||||||
|
* 设置 runningTasks 中的任务
|
||||||
|
*/
|
||||||
|
const loadRunningTask = (tasks) => {
|
||||||
|
runningTask.value = {}
|
||||||
|
auditForm.value = {}
|
||||||
|
approveForm.value = {}
|
||||||
|
approveFormFApi.value = {}
|
||||||
|
tasks.forEach((task) => {
|
||||||
|
if (!isEmpty(task.children)) {
|
||||||
|
loadRunningTask(task.children)
|
||||||
|
}
|
||||||
|
// 2.1 只有待处理才需要
|
||||||
|
if (task.status !== 1 && task.status !== 6) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 2.2 自己不是处理人
|
||||||
|
if (!task.assigneeUser || task.assigneeUser.id !== userId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 2.3 添加到处理任务
|
||||||
|
runningTask.value = { ...task }
|
||||||
|
auditForm.value = {
|
||||||
|
reason: '',
|
||||||
|
copyUserIds: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.4 处理 approve 表单
|
||||||
|
if (task.formId && task.formConf) {
|
||||||
|
const tempApproveForm = {}
|
||||||
|
setConfAndFields2(tempApproveForm, task.formConf, task.formFields, task.formVariables)
|
||||||
|
approveForm.value = tempApproveForm
|
||||||
|
} else {
|
||||||
|
approveForm.value = {} // 占位,避免为空
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理审批通过和不通过的操作 */
|
||||||
|
const handleAudit = async (pass) => {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const auditFormRef = proxy.$refs['formRef']
|
||||||
|
// 1.2 校验表单
|
||||||
|
const elForm = unref(auditFormRef)
|
||||||
|
if (!elForm) return
|
||||||
|
const valid = await elForm.validate()
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
// 2.1 提交审批
|
||||||
|
const data = {
|
||||||
|
id: runningTask.value.id,
|
||||||
|
reason: auditForm.value.reason,
|
||||||
|
copyUserIds: auditForm.value.copyUserIds
|
||||||
|
}
|
||||||
|
if (pass) {
|
||||||
|
// 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||||
|
const formCreateApi = approveFormFApi.value
|
||||||
|
if (Object.keys(formCreateApi)?.length > 0) {
|
||||||
|
await formCreateApi.validate()
|
||||||
|
// @ts-ignore
|
||||||
|
data.variables = approveForm.value.value
|
||||||
|
}
|
||||||
|
await TaskApi.approveTask(data)
|
||||||
|
message.success('审批通过成功')
|
||||||
|
} else {
|
||||||
|
await TaskApi.rejectTask(data)
|
||||||
|
message.success('审批不通过成功')
|
||||||
|
}
|
||||||
|
// 2.2 加载最新数据
|
||||||
|
getDetail()
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 抄送 TODO */
|
||||||
|
const handleSend = () => {}
|
||||||
|
|
||||||
|
const openPopover = (flag) => {
|
||||||
|
passVisible.value = false
|
||||||
|
rejectVisible.value = false
|
||||||
|
formRef.value.resetFields()
|
||||||
|
flag === '1' ? (passVisible.value = true) : (rejectVisible.value = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 转派审批人 */
|
||||||
|
const taskTransferFormRef = ref()
|
||||||
|
const openTaskUpdateAssigneeForm = () => {
|
||||||
|
taskTransferFormRef.value.open(runningTask.value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理审批退回的操作 */
|
||||||
|
const taskDelegateForm = ref()
|
||||||
|
const handleDelegate = async () => {
|
||||||
|
taskDelegateForm.value.open(runningTask.value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理审批退回的操作 */
|
||||||
|
const taskReturnFormRef = ref()
|
||||||
|
const handleBack = async () => {
|
||||||
|
taskReturnFormRef.value.open(runningTask.value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理审批加签的操作 */
|
||||||
|
const taskSignCreateFormRef = ref()
|
||||||
|
const handleSign = async () => {
|
||||||
|
taskSignCreateFormRef.value.open(runningTask.value.id)
|
||||||
|
}
|
||||||
|
/** 获得详情 */
|
||||||
|
const getDetail = () => {
|
||||||
|
emit('success')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ loadRunningTask })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-affix--fixed) {
|
||||||
|
background-color: var(--el-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
> div {
|
||||||
|
margin: 0 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&:hover {
|
||||||
|
color: #6db5ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,161 @@
|
||||||
|
<template>
|
||||||
|
<el-timeline class="pt-20px">
|
||||||
|
<el-timeline-item v-for="(activity, index) in mockData" :key="index" size="large">
|
||||||
|
<div class="flex flex-col items-start">
|
||||||
|
<div class="font-bold"> {{ activity.name }}</div>
|
||||||
|
<div class="color-#a1a6ae text-12px mb-10px"> {{ activity.assigneeUser.nickname }}</div>
|
||||||
|
<div v-if="activity.opinion" class="text-#a5a5a5 text-12px w-100%">
|
||||||
|
<div class="mb-5px">审批意见:</div>
|
||||||
|
<div
|
||||||
|
class="w-100% border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
|
||||||
|
>
|
||||||
|
{{ activity.opinion }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="activity.createTime" class="text-#a5a5a5 text-13px">
|
||||||
|
{{ formatDate(activity.createTime) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 该节点用户的头像 -->
|
||||||
|
<template #dot>
|
||||||
|
<div class="w-35px h-35px position-relative">
|
||||||
|
<img
|
||||||
|
src="@/assets/imgs/avatar.jpg"
|
||||||
|
class="rounded-full w-full h-full position-absolute bottom-6px right-12px"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="position-absolute top-16px left-8px bg-#fff rounded-full flex items-center content-center p-2px"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
:size="12"
|
||||||
|
:icon="optIconMap[activity.status]?.icon"
|
||||||
|
:color="optIconMap[activity.status]?.color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { formatDate } from '@/utils/formatTime'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
|
||||||
|
defineOptions({ name: 'BpmProcessInstanceTimeline' })
|
||||||
|
defineProps({
|
||||||
|
tasks: propTypes.array // 流程任务的数组
|
||||||
|
})
|
||||||
|
|
||||||
|
const optIconMap = {
|
||||||
|
// 审批中
|
||||||
|
'1': {
|
||||||
|
color: '#00b32a',
|
||||||
|
icon: 'fa-solid:clock'
|
||||||
|
},
|
||||||
|
// 审批通过
|
||||||
|
'2': { color: '#00b32a', icon: 'fa-solid:check-circle' },
|
||||||
|
// 审批不通过
|
||||||
|
'3': { color: '#f46b6c', icon: 'fa-solid:times-circle' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockData: any = [
|
||||||
|
{
|
||||||
|
id: 'fe1190ee-68c3-11ef-9c7d-00a6181404fd',
|
||||||
|
name: '发起人',
|
||||||
|
createTime: 1725237646192,
|
||||||
|
endTime: null,
|
||||||
|
durationInMillis: null,
|
||||||
|
status: 1,
|
||||||
|
reason: null,
|
||||||
|
ownerUser: null,
|
||||||
|
assigneeUser: {
|
||||||
|
id: 104,
|
||||||
|
nickname: '测试号',
|
||||||
|
deptId: 107,
|
||||||
|
deptName: '运维部门'
|
||||||
|
},
|
||||||
|
taskDefinitionKey: 'task-01',
|
||||||
|
processInstanceId: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
||||||
|
processInstance: {
|
||||||
|
id: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
||||||
|
name: 'oa_leave',
|
||||||
|
createTime: null,
|
||||||
|
processDefinitionId: 'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd',
|
||||||
|
startUser: null
|
||||||
|
},
|
||||||
|
parentTaskId: null,
|
||||||
|
children: null,
|
||||||
|
formId: null,
|
||||||
|
formName: null,
|
||||||
|
formConf: null,
|
||||||
|
formFields: null,
|
||||||
|
formVariables: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fe1190ee-68c3-11ef-9c7d-00a6181404fd',
|
||||||
|
name: '领导审批',
|
||||||
|
createTime: 1725237646192,
|
||||||
|
endTime: null,
|
||||||
|
durationInMillis: null,
|
||||||
|
status: 2,
|
||||||
|
reason: null,
|
||||||
|
ownerUser: null,
|
||||||
|
assigneeUser: {
|
||||||
|
id: 104,
|
||||||
|
nickname: '领导',
|
||||||
|
deptId: 107,
|
||||||
|
deptName: '运维部门'
|
||||||
|
},
|
||||||
|
taskDefinitionKey: 'task-01',
|
||||||
|
processInstanceId: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
||||||
|
processInstance: {
|
||||||
|
id: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
||||||
|
name: 'oa_leave',
|
||||||
|
createTime: null,
|
||||||
|
processDefinitionId: 'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd',
|
||||||
|
startUser: null
|
||||||
|
},
|
||||||
|
parentTaskId: null,
|
||||||
|
children: null,
|
||||||
|
formId: null,
|
||||||
|
formName: null,
|
||||||
|
formConf: null,
|
||||||
|
formFields: null,
|
||||||
|
formVariables: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fe1190ee-68c3-11ef-9c7d-00a6181404fd',
|
||||||
|
name: '财务总监审核',
|
||||||
|
createTime: 1725237646192,
|
||||||
|
endTime: null,
|
||||||
|
durationInMillis: null,
|
||||||
|
status: 3,
|
||||||
|
reason: null,
|
||||||
|
ownerUser: null,
|
||||||
|
assigneeUser: {
|
||||||
|
id: 104,
|
||||||
|
nickname: '财务总监',
|
||||||
|
deptId: 107,
|
||||||
|
deptName: '运维部门'
|
||||||
|
},
|
||||||
|
taskDefinitionKey: 'task-01',
|
||||||
|
processInstanceId: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
||||||
|
processInstance: {
|
||||||
|
id: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
||||||
|
name: 'oa_leave',
|
||||||
|
createTime: null,
|
||||||
|
processDefinitionId: 'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd',
|
||||||
|
startUser: null
|
||||||
|
},
|
||||||
|
parentTaskId: null,
|
||||||
|
children: null,
|
||||||
|
formId: null,
|
||||||
|
formName: null,
|
||||||
|
formConf: null,
|
||||||
|
formFields: null,
|
||||||
|
formVariables: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
|
@ -9,7 +9,7 @@
|
||||||
>
|
>
|
||||||
<el-form-item label="减签任务" prop="id">
|
<el-form-item label="减签任务" prop="id">
|
||||||
<el-radio-group v-model="formData.id">
|
<el-radio-group v-model="formData.id">
|
||||||
<el-radio-button v-for="item in childrenTaskList" :key="item.id" :label="item.id">
|
<el-radio-button v-for="item in childrenTaskList" :key="item.id" :value="item.id">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
({{ item.assigneeUser?.deptName || item.ownerUser?.deptName }} -
|
({{ item.assigneeUser?.deptName || item.ownerUser?.deptName }} -
|
||||||
{{ item.assigneeUser?.nickname || item.ownerUser?.nickname }})
|
{{ item.assigneeUser?.nickname || item.ownerUser?.nickname }})
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
<template>
|
||||||
|
<ContentWrap :bodyStyle="{ padding: '10px 20px' }" class="position-relative">
|
||||||
|
<img
|
||||||
|
class="position-absolute right-20px"
|
||||||
|
width="150"
|
||||||
|
:src="auditIcons[processInstance.status]"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<div class="text-#878c93">编号:{{ id }}</div>
|
||||||
|
<el-divider class="!my-8px" />
|
||||||
|
<div class="flex items-center gap-5 mb-10px">
|
||||||
|
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
|
||||||
|
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="processInstance.status" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-5 mb-10px text-13px">
|
||||||
|
<div class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600">
|
||||||
|
<img class="rounded-full h-28px" src="@/assets/imgs/avatar.jpg" alt="" />
|
||||||
|
{{ processInstance?.startUser?.nickname }}
|
||||||
|
</div>
|
||||||
|
<div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-tabs>
|
||||||
|
<!-- 表单信息 -->
|
||||||
|
<el-tab-pane label="表单信息">
|
||||||
|
<el-row :gutter="10">
|
||||||
|
<el-col :span="18" class="!flex !flex-col formCol">
|
||||||
|
<!-- 表单信息 -->
|
||||||
|
<div v-loading="processInstanceLoading" class="form-box flex flex-col mb-30px flex-1">
|
||||||
|
<!-- 情况一:流程表单 -->
|
||||||
|
<el-col
|
||||||
|
v-if="processInstance?.processDefinition?.formType === 10"
|
||||||
|
:offset="6"
|
||||||
|
:span="16"
|
||||||
|
>
|
||||||
|
<form-create
|
||||||
|
v-model="detailForm.value"
|
||||||
|
v-model:api="fApi"
|
||||||
|
:option="detailForm.option"
|
||||||
|
:rule="detailForm.rule"
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
<!-- 情况二:业务表单 -->
|
||||||
|
<div v-if="processInstance?.processDefinition?.formType === 20">
|
||||||
|
<BusinessFormComponent :id="processInstance.businessKey" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作栏按钮 -->
|
||||||
|
<ProcessInstanceOperationButton
|
||||||
|
ref="operationButtonRef"
|
||||||
|
:processInstance="processInstance"
|
||||||
|
:userOptions="userOptions"
|
||||||
|
@success="getDetail"
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<!-- 审批记录时间线 -->
|
||||||
|
<ProcessInstanceTimeline :process-instance="processInstance" :tasks="tasks" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-tab-pane>
|
||||||
|
<!-- 流程图 -->
|
||||||
|
<el-tab-pane label="流程图">
|
||||||
|
<ProcessInstanceBpmnViewer
|
||||||
|
:id="`${id}`"
|
||||||
|
:bpmn-xml="bpmnXml"
|
||||||
|
:loading="processInstanceLoading"
|
||||||
|
:process-instance="processInstance"
|
||||||
|
:tasks="tasks"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<!-- 流转记录 -->
|
||||||
|
<el-tab-pane label="流转记录">
|
||||||
|
<ProcessInstanceTaskList
|
||||||
|
:loading="tasksLoad"
|
||||||
|
:process-instance="processInstance"
|
||||||
|
:tasks="tasks"
|
||||||
|
@refresh="getTaskList"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<!-- 流转评论 -->
|
||||||
|
<el-tab-pane label="流转评论"> 流转评论 </el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { formatDate } from '@/utils/formatTime'
|
||||||
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||||
|
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||||
|
import * as DefinitionApi from '@/api/bpm/definition'
|
||||||
|
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||||
|
import * as TaskApi from '@/api/bpm/task'
|
||||||
|
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
|
||||||
|
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
||||||
|
import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue'
|
||||||
|
import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
|
||||||
|
import { registerComponent } from '@/utils/routerHelper'
|
||||||
|
import * as UserApi from '@/api/system/user'
|
||||||
|
import audit1 from '@/assets/svgs/bpm/audit1.svg'
|
||||||
|
import audit2 from '@/assets/svgs/bpm/audit2.svg'
|
||||||
|
import audit3 from '@/assets/svgs/bpm/audit3.svg'
|
||||||
|
|
||||||
|
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||||
|
|
||||||
|
const { query } = useRoute() // 查询参数
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const id = query.id as unknown as string // 流程实例的编号
|
||||||
|
const processInstanceLoading = ref(false) // 流程实例的加载中
|
||||||
|
const processInstance = ref<any>({}) // 流程实例
|
||||||
|
const operationButtonRef = ref()
|
||||||
|
const bpmnXml = ref('') // BPMN XML
|
||||||
|
const tasksLoad = ref(true) // 任务的加载中
|
||||||
|
const tasks = ref<any[]>([]) // 任务列表
|
||||||
|
const auditIcons = {
|
||||||
|
1: audit1,
|
||||||
|
2: audit2,
|
||||||
|
3: audit3
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 申请信息 ==========
|
||||||
|
const fApi = ref<ApiAttrs>() //
|
||||||
|
const detailForm = ref({
|
||||||
|
rule: [],
|
||||||
|
option: {},
|
||||||
|
value: {}
|
||||||
|
}) // 流程实例的表单详情
|
||||||
|
|
||||||
|
/** 获得详情 */
|
||||||
|
const getDetail = () => {
|
||||||
|
// 1. 获得流程实例相关
|
||||||
|
getProcessInstance()
|
||||||
|
// 2. 获得流程任务列表(审批记录)
|
||||||
|
getTaskList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 加载流程实例 */
|
||||||
|
const BusinessFormComponent = ref<any>(null) // 异步组件
|
||||||
|
const getProcessInstance = async () => {
|
||||||
|
try {
|
||||||
|
processInstanceLoading.value = true
|
||||||
|
const data = await ProcessInstanceApi.getProcessInstance(id)
|
||||||
|
if (!data) {
|
||||||
|
message.error('查询不到流程信息!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
processInstance.value = data
|
||||||
|
|
||||||
|
// 设置表单信息
|
||||||
|
const processDefinition = data.processDefinition
|
||||||
|
if (processDefinition.formType === 10) {
|
||||||
|
setConfAndFields2(
|
||||||
|
detailForm,
|
||||||
|
processDefinition.formConf,
|
||||||
|
processDefinition.formFields,
|
||||||
|
data.formVariables
|
||||||
|
)
|
||||||
|
nextTick().then(() => {
|
||||||
|
fApi.value?.btn.show(false)
|
||||||
|
fApi.value?.resetBtn.show(false)
|
||||||
|
fApi.value?.disabled(true)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
||||||
|
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载流程图
|
||||||
|
bpmnXml.value = (await DefinitionApi.getProcessDefinition(processDefinition.id))?.bpmnXml
|
||||||
|
} finally {
|
||||||
|
processInstanceLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 加载任务列表 */
|
||||||
|
const getTaskList = async () => {
|
||||||
|
try {
|
||||||
|
// 获得未取消的任务
|
||||||
|
tasksLoad.value = true
|
||||||
|
const data = await TaskApi.getTaskListByProcessInstanceId(id)
|
||||||
|
tasks.value = []
|
||||||
|
// 1.1 移除已取消的审批
|
||||||
|
data.forEach((task) => {
|
||||||
|
if (task.status !== 4) {
|
||||||
|
tasks.value.push(task)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 1.2 排序,将未完成的排在前面,已完成的排在后面;
|
||||||
|
tasks.value.sort((a, b) => {
|
||||||
|
// 有已完成的情况,按照完成时间倒序
|
||||||
|
if (a.endTime && b.endTime) {
|
||||||
|
return b.endTime - a.endTime
|
||||||
|
} else if (a.endTime) {
|
||||||
|
return 1
|
||||||
|
} else if (b.endTime) {
|
||||||
|
return -1
|
||||||
|
// 都是未完成,按照创建时间倒序
|
||||||
|
} else {
|
||||||
|
return b.createTime - a.createTime
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获得需要自己审批的任务
|
||||||
|
operationButtonRef.value?.loadRunningTask(tasks.value)
|
||||||
|
} finally {
|
||||||
|
tasksLoad.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
|
onMounted(async () => {
|
||||||
|
getDetail()
|
||||||
|
// 获得用户列表
|
||||||
|
userOptions.value = await UserApi.getSimpleUserList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.form-box {
|
||||||
|
:deep(.el-card) {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -76,7 +76,7 @@
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
end-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-220px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.value"
|
:value="dict.value"
|
||||||
>
|
>
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue