Merge remote-tracking branch 'yudao/dev' into dev-to-dev

pull/264/head
puhui999 2023-10-05 21:50:33 +08:00
commit 869bac2501
146 changed files with 3359 additions and 1302 deletions

View File

@ -22,7 +22,8 @@ module.exports = defineConfig({
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended'
'plugin:prettier/recommended',
'@unocss'
],
rules: {
'vue/script-setup-uses-vars': 'error',

73
.vscode/settings.json vendored
View File

@ -1,7 +1,5 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"volar.tsPlugin": true,
"volar.tsPluginStatus": false,
"npm.packageManager": "pnpm",
"editor.tabSize": 2,
"prettier.printWidth": 100, //
@ -102,57 +100,35 @@
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"xingyu",
"yudao",
"unocss",
"brotli",
"browserslist",
"codemirror",
"commitlint",
"cropperjs",
"echarts",
"esnext",
"unplugin",
"esno",
"iconify",
"INTLIFY",
"lintstagedrc",
"logicflow",
"nprogress",
"pinia",
"pnpm",
"qrcode",
"sider",
"pinia",
"sider",
"nprogress",
"INTLIFY",
"stylelint",
"esno",
"vitejs",
"sortablejs",
"codemirror",
"iconify",
"commitlint",
"stylelint",
"unocss",
"unplugin",
"unref",
"videojs",
"echarts",
"wangeditor",
"cropperjs",
"logicflow",
"vitejs",
"vueuse",
"zxcvbn",
"lintstagedrc",
"brotli",
"sider",
"pnpm"
],
"vetur.format.scriptInitialIndent": true,
"vetur.format.styleInitialIndent": true,
"vetur.validation.script": false,
"MicroPython.executeButton": [
{
"text": "▶",
"tooltip": "运行",
"alignment": "left",
"command": "extension.executeFile",
"priority": 3.5
}
],
"MicroPython.syncButton": [
{
"text": "$(sync)",
"tooltip": "同步",
"alignment": "left",
"command": "extension.execute",
"priority": 4
}
"wangeditor",
"xingyu",
"yudao",
"zxcvbn"
],
//
"explorer.fileNesting.enabled": true,
@ -161,7 +137,8 @@
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
"*.env": "$(capture).env.*",
"package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
"package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.eslintrc-auto-import.json,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
},
"terminal.integrated.scrollback": 10000
"terminal.integrated.scrollback": 10000,
"nuxt.isNuxtApp": false
}

View File

@ -9,7 +9,7 @@
## 🐶 新手必读
* nodejs > 16.0.0 && pnpm > 8.6.0 (强制使用pnpm)
* nodejs > 16.18.0 && pnpm > 8.6.0 (强制使用pnpm)
* 演示地址【Vue3 + element-plus】<http://dashboard-vue3.yudao.iocoder.cn>
* 演示地址【Vue3 + vben(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
* 演示地址【Vue2 + element-ui】<http://dashboard.yudao.iocoder.cn>
@ -40,13 +40,13 @@
|----------------------------------------------------------------------|------------------|--------|
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.3.4 |
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.4.9 |
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.3.12 |
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.1.6 |
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.3.14 |
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.2.2 |
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.6 |
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.4.1 |
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.4 |
| [unocss](https://uno.antfu.me/) | 原子 css | 0.55.3 |
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.4.1 |
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.5 |
| [unocss](https://uno.antfu.me/) | 原子 css | 0.56.1 |
| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.1.1 |
| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.23 |

View File

@ -1,6 +1,6 @@
{
"name": "yudao-ui-admin-vue3",
"version": "1.8.0-snapshot",
"version": "1.8.2-snapshot",
"description": "基于vue3、vite4、element-plus、typesScript",
"author": "xingyu",
"private": false,
@ -22,32 +22,32 @@
"clean:cache": "npx rimraf node_modules/.cache",
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:style": "stylelint --fix \"./src/**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c "
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"@form-create/designer": "^3.1.0",
"@form-create/element-ui": "^3.1.17",
"@form-create/designer": "^3.1.3",
"@form-create/element-ui": "^3.1.24",
"@iconify/iconify": "^3.1.1",
"@videojs-player/vue": "^1.0.0",
"@vueuse/core": "^10.4.1",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.10",
"@zxcvbn-ts/core": "^3.0.3",
"@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1",
"axios": "^1.5.0",
"benz-amr-recorder": "^1.1.5",
"bpmn-js-token-simulation": "^0.10.0",
"camunda-bpmn-moddle": "^7.0.1",
"cropperjs": "^1.6.0",
"cropperjs": "^1.6.1",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.9",
"dayjs": "^1.11.10",
"diagram-js": "^12.3.0",
"echarts": "^5.4.3",
"echarts-wordcloud": "^2.1.0",
"element-plus": "2.3.12",
"fast-xml-parser": "^4.2.7",
"element-plus": "2.3.14",
"fast-xml-parser": "^4.3.0",
"highlight.js": "^11.8.0",
"intro.js": "^7.2.0",
"jsencrypt": "^3.3.2",
@ -59,12 +59,12 @@
"qrcode": "^1.5.3",
"qs": "^6.11.2",
"steady-xml": "^0.1.0",
"url": "^0.11.1",
"url": "^0.11.3",
"video.js": "^7.21.5",
"vue": "3.3.4",
"vue": "^3.3.4",
"vue-dompurify-html": "^4.1.4",
"vue-i18n": "9.2.2",
"vue-router": "^4.2.4",
"vue-i18n": "^9.4.1",
"vue-router": "^4.2.5",
"vue-types": "^5.1.1",
"vuedraggable": "^4.1.0",
"web-storage-cache": "^1.1.1",
@ -73,50 +73,51 @@
"devDependencies": {
"@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0",
"@iconify/json": "^2.2.107",
"@intlify/unplugin-vue-i18n": "^0.12.3",
"@iconify/json": "^2.2.119",
"@intlify/unplugin-vue-i18n": "^1.2.0",
"@purge-icons/generated": "^0.9.0",
"@types/intro.js": "^5.1.1",
"@types/lodash-es": "^4.17.8",
"@types/node": "^20.5.0",
"@types/lodash-es": "^4.17.9",
"@types/node": "^20.6.0",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.5.1",
"@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "^6.4.1",
"@typescript-eslint/parser": "^6.4.1",
"@unocss/transformer-variant-group": "^0.55.3",
"@types/qrcode": "^1.5.2",
"@types/qs": "^6.9.8",
"@typescript-eslint/eslint-plugin": "^6.7.2",
"@typescript-eslint/parser": "^6.7.2",
"@unocss/transformer-variant-group": "^0.56.1",
"@unocss/eslint-config": "^0.56.1",
"@vitejs/plugin-legacy": "^4.1.1",
"@vitejs/plugin-vue": "^4.3.3",
"@vitejs/plugin-vue": "^4.3.4",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue-macros/volar": "^0.14.2",
"autoprefixer": "^10.4.15",
"bpmn-js": "^8.9.0",
"bpmn-js-properties-panel": "^0.46.0",
"@vue-macros/volar": "^0.14.3",
"autoprefixer": "^10.4.16",
"bpmn-js": "8.9.0",
"bpmn-js-properties-panel": "0.46.0",
"consola": "^3.2.3",
"eslint": "^8.48.0",
"eslint": "^8.49.0",
"eslint-config-prettier": "^9.0.0",
"eslint-define-config": "^1.23.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.17.0",
"lint-staged": "^14.0.1",
"postcss": "^8.4.28",
"postcss": "^8.4.30",
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.7",
"prettier": "^3.0.2",
"postcss-scss": "^4.0.8",
"prettier": "^3.0.3",
"rimraf": "^5.0.1",
"rollup": "^3.28.1",
"sass": "^1.66.1",
"rollup": "^3.29.2",
"sass": "^1.68.0",
"stylelint": "^15.10.3",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-order": "^6.0.3",
"terser": "^5.19.2",
"typescript": "5.1.6",
"unocss": "^0.55.3",
"terser": "^5.20.0",
"typescript": "5.2.2",
"unocss": "^0.56.1",
"unplugin-auto-import": "^0.16.6",
"unplugin-element-plus": "^0.8.0",
"unplugin-vue-components": "^0.25.1",
"unplugin-vue-components": "^0.25.2",
"vite": "4.4.9",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-ejs": "^1.6.4",
@ -126,7 +127,7 @@
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-top-level-await": "^1.3.1",
"vue-eslint-parser": "^9.3.1",
"vue-tsc": "^1.8.8"
"vue-tsc": "^1.8.13"
},
"license": "MIT",
"repository": {

View File

@ -41,3 +41,20 @@ export const getTaskListByProcessInstanceId = async (processInstanceId) => {
export const exportTask = async (params) => {
return await request.download({ url: '/bpm/task/export', params })
}
// 获取所有可回退的节点
export const getReturnList = async (params) => {
return await request.get({ url: '/bpm/task/get-return-list', params })
}
// 回退
export const returnTask = async (data) => {
return await request.put({ url: '/bpm/task/return', data })
}
/**
*
*/
export const delegateTask = async (data) => {
return await request.put({ url: '/bpm/task/delegate', data })
}

View File

@ -7,17 +7,16 @@ export interface BargainActivityVO {
startTime?: Date
endTime?: Date
status?: number
userSize?: number // 达到该人数,才能砍到低价
helpMaxCount?: number // 达到该人数,才能砍到低价
bargainCount?: number // 最大帮砍次数
totalLimitCount?: number // 最大购买次数
spuId: number
skuId: number
bargainFirstPrice: number // 砍价起始价格,单位分
bargainPrice: number // 砍价底价
bargainMinPrice: number // 砍价底价
stock: number // 活动库存
randomMinPrice?: number // 用户每次砍价的最小金额,单位:分
randomMaxPrice?: number // 用户每次砍价的最大金额,单位:分
successCount?: number // 砍价成功数量
}
// 砍价活动所需属性。选择的商品和属性的时候使用方便使用活动的通用封装

View File

@ -0,0 +1,14 @@
import request from '@/config/axios'
export interface BargainHelpVO {
id: number
record: number
userId: number
reducePrice: number
endTime: Date
}
// 查询砍价记录列表
export const getBargainHelpPage = async (params) => {
return await request.get({ url: `/promotion/bargain-help/page`, params })
}

View File

@ -0,0 +1,19 @@
import request from '@/config/axios'
export interface BargainRecordVO {
id: number
activityId: number
userId: number
spuId: number
skuId: number
bargainFirstPrice: number
bargainPrice: number
status: number
orderId: number
endTime: Date
}
// 查询砍价记录列表
export const getBargainRecordPage = async (params) => {
return await request.get({ url: `/promotion/bargain-record/page`, params })
}

View File

@ -57,6 +57,11 @@ export const updateSeckillActivity = async (data: SeckillActivityVO) => {
return await request.put({ url: '/promotion/seckill-activity/update', data })
}
// 关闭秒杀活动
export const closeSeckillActivity = async (id: number) => {
return await request.put({ url: '/promotion/seckill-activity/close?id=' + id })
}
// 删除秒杀活动
export const deleteSeckillActivity = async (id: number) => {
return await request.delete({ url: '/promotion/seckill-activity/delete?id=' + id })

View File

@ -4,13 +4,13 @@ export interface ConfigVO {
brokerageEnabled: boolean
brokerageEnabledCondition: number
brokerageBindMode: number
brokeragePostUrls: string
brokeragePosterUrls: string
brokerageFirstPercent: number
brokerageSecondPercent: number
brokerageWithdrawMinPrice: number
brokerageBankNames: string
brokerageFrozenDays: number
brokerageWithdrawType: string
brokerageWithdrawTypes: string
}
// 查询交易中心配置详情

View File

@ -41,15 +41,22 @@ export interface OrderVO {
refundPrice?: number | null // 退款金额
couponId?: number | null // 优惠劵编号
couponPrice?: number | null // 优惠劵减免金额
vipPrice?: number | null // VIP 减免金额
pointPrice?: number | null // 积分抵扣的金额
receiverAreaName?: string //收件人地区名字
items?: OrderItemRespVO[] // 订单项列表
// 用户信息
// 下单用户信息
user?: {
id?: number | null
nickname?: string
avatar?: string
}
// 推广用户信息
brokerageUser?: {
id?: number | null
nickname?: string
avatar?: string
}
// 订单操作日志
logs?: OrderLogRespVO[]
}
@ -114,21 +121,26 @@ export interface DeliveryVO {
}
// 订单发货
export const delivery = async (data: DeliveryVO) => {
export const deliveryOrder = async (data: DeliveryVO) => {
return await request.put({ url: `/trade/order/delivery`, data })
}
// 订单备注
export const updateRemark = async (data: any) => {
export const updateOrderRemark = async (data: any) => {
return await request.put({ url: `/trade/order/update-remark`, data })
}
// 订单调价
export const updatePrice = async (data: any) => {
export const updateOrderPrice = async (data: any) => {
return await request.put({ url: `/trade/order/update-price`, data })
}
// 修改订单地址
export const updateAddress = async (data: any) => {
export const updateOrderAddress = async (data: any) => {
return await request.put({ url: `/trade/order/update-address`, data })
}
// 订单核销
export const pickUpOrder = async (id: number) => {
return await request.put({ url: `/trade/order/pick-up?id=${id}` })
}

View File

@ -0,0 +1,19 @@
import request from '@/config/axios'
export interface ConfigVO {
id: number
pointTradeDeductEnable: number
pointTradeDeductUnitPrice: number
pointTradeDeductMaxPrice: number
pointTradeGivePoint: number
}
// 查询积分设置详情
export const getConfig = async () => {
return await request.get({ url: `/member/config/get` })
}
// 新增修改积分设置
export const saveConfig = async (data: ConfigVO) => {
return await request.put({ url: `/member/config/save`, data })
}

View File

@ -1,19 +0,0 @@
import request from '@/config/axios'
export interface ConfigVO {
id: number
tradeDeductEnable: number
tradeDeductUnitPrice: number
tradeDeductMaxPrice: number
tradeGivePoint: number
}
// 查询积分设置详情
export const getConfig = async () => {
return await request.get({ url: `/member/point/config/get` })
}
// 新增修改积分设置
export const saveConfig = async (data: ConfigVO) => {
return await request.put({ url: `/member/point/config/save`, data })
}

View File

@ -1,10 +1,11 @@
import request from '@/config/axios'
export interface SignInConfigVO {
id: number
day: number | null
point: number | null
enable: boolean | null
id?: number
day?: number
point?: number
experience?: number
status?: number
}
// 查询积分签到规则列表

View File

@ -41,3 +41,13 @@ export const updateUser = async (data: UserVO) => {
export const updateUserLevel = async (data: any) => {
return await request.put({ url: `/member/user/update-level`, data })
}
// 修改会员用户积分
export const updateUserPoint = async (data: any) => {
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 })
}

View File

@ -0,0 +1,22 @@
import request from '@/config/axios'
/** 用户钱包查询参数 */
export interface PayWalletUserReqVO {
userId: number
userType: number
}
/** 钱包 VO */
export interface WalletVO {
id: number
userId: number
userType: number
balance: number
totalExpense: number
totalRecharge: number
freezePrice: number
}
/** 查询用户钱包详情 */
export const getWallet = async (params: PayWalletUserReqVO) => {
return await request.get<WalletVO>({ url: `/pay/wallet/get`, params })
}

View File

@ -0,0 +1,72 @@
import request from '@/config/axios'
import dayjs from 'dayjs'
import { formatDate } from '@/utils/formatTime'
// todo @疯狂:挪到 mall 里哈
/** 交易统计对照 Response VO */
export interface TradeStatisticsComparisonRespVO<T> {
value: T
reference: T
}
/** 交易统计 Response VO */
export interface TradeSummaryRespVO {
yesterdayOrderCount: number
monthOrderCount: number
yesterdayPayPrice: number
monthPayPrice: number
}
/** 交易状况 Request VO */
export interface TradeTrendReqVO {
times: [dayjs.ConfigType, dayjs.ConfigType]
}
/** 交易状况统计 Response VO */
export interface TradeTrendSummaryRespVO {
time: string
turnover: number
orderPayPrice: number
rechargePrice: number
expensePrice: number
balancePrice: number
brokerageSettlementPrice: number
orderRefundPrice: number
}
// 查询交易统计
export const getTradeStatisticsSummary = () => {
return request.get<TradeStatisticsComparisonRespVO<TradeSummaryRespVO>>({
url: '/statistics/trade/summary'
})
}
// 获得交易状况统计
export const getTradeTrendSummary = (params: TradeTrendReqVO) => {
return request.get<TradeStatisticsComparisonRespVO<TradeTrendSummaryRespVO>>({
url: '/statistics/trade/trend/summary',
params: formatDateParam(params)
})
}
// 获得交易状况明细
export const getTradeTrendList = (params: TradeTrendReqVO) => {
return request.get<TradeTrendSummaryRespVO[]>({
url: '/statistics/trade/trend/list',
params: formatDateParam(params)
})
}
// 导出交易状况明细
export const exportTradeTrend = (params: TradeTrendReqVO) => {
return request.download({
url: '/statistics/trade/trend/export-excel',
params: formatDateParam(params)
})
}
/** 时间参数需要格式化, 确保接口能识别 */
const formatDateParam = (params: TradeTrendReqVO) => {
return { times: [formatDate(params.times[0]), formatDate(params.times[1])] } as TradeTrendReqVO
}

View File

@ -5,14 +5,6 @@ export const getAreaTree = async () => {
return await request.get({ url: '/system/area/tree' })
}
export const getChildrenArea = async (id: number) => {
return await request.get({ url: '/system/area/get-children?id=' + id })
}
export const getAreaListByIds = async (ids) => {
return await request.get({ url: '/system/area/get-by-ids?ids=' + ids })
}
// 获得 IP 对应的地区名
export const getAreaByIp = async (ip: string) => {
return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })

View File

@ -19,17 +19,18 @@ const { title } = defineProps({
.card-title {
font-size: 14px;
font-weight: 600;
&::before {
content: '';
position: relative;
top: 8px;
left: -5px;
display: inline-block;
width: 3px;
height: 14px;
//background-color: #105cfb;
background: var(--el-color-primary);
position: relative;
left: -5px;
top: 8px;
border-radius: 5px;
content: '';
transform: translateY(-50%);
}
}

View File

@ -11,21 +11,21 @@ const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('count-to')
const props = defineProps({
startVal: propTypes.number.def(0),
endVal: propTypes.number.def(2021),
duration: propTypes.number.def(3000),
autoplay: propTypes.bool.def(true),
decimals: propTypes.number.validate((value: number) => value >= 0).def(0),
decimal: propTypes.string.def('.'),
separator: propTypes.string.def(','),
prefix: propTypes.string.def(''),
suffix: propTypes.string.def(''),
useEasing: propTypes.bool.def(true),
startVal: propTypes.number.def(0), //
endVal: propTypes.number.def(2021), //
duration: propTypes.number.def(3000), //
autoplay: propTypes.bool.def(true), // ,
decimals: propTypes.number.validate((value: number) => value >= 0).def(0), // ,
decimal: propTypes.string.def('.'), // ,
separator: propTypes.string.def(','), // ,
prefix: propTypes.string.def(''), // ,
suffix: propTypes.string.def(''), // ,
useEasing: propTypes.bool.def(true), // 使,
easingFn: {
type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
default(t: number, b: number, c: number, d: number) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
}
} //
}
})

View File

@ -1,7 +1,7 @@
<template>
<div class="user-info-head" @click="open()">
<img v-if="sourceValue" :src="sourceValue" alt="avatar" class="img-circle img-lg" />
<img v-if="!sourceValue" :src="avatar" alt="avatar" class="img-circle img-lg" />
<el-avatar v-if="sourceValue" :src="sourceValue" alt="avatar" class="img-circle img-lg" />
<el-avatar v-if="!sourceValue" :src="avatar" alt="avatar" class="img-circle img-lg" />
<el-button v-if="showBtn" :class="`${prefixCls}-upload-btn`" @click="open()">
{{ btnText ? btnText : t('cropper.selectImage') }}
</el-button>

View File

@ -22,6 +22,7 @@ const { label } = defineProps({
.cell-item {
display: inline;
}
.cell-item::after {
content: ':';
}

View File

@ -66,28 +66,27 @@ const dialogStyle = computed(() => {
destroy-on-close
lock-scroll
draggable
top="0"
class="com-dialog"
:show-close="false"
>
<template #header="{ close }">
<div class="flex justify-between items-center h-54px pl-15px pr-15px relative">
<div class="relative h-54px flex items-center justify-between pl-15px pr-15px">
<slot name="title">
{{ title }}
</slot>
<div
class="h-54px flex justify-between items-center absolute top-[50%] right-15px translate-y-[-50%]"
class="absolute right-15px top-[50%] h-54px flex translate-y-[-50%] items-center justify-between"
>
<Icon
v-if="fullscreen"
class="cursor-pointer is-hover mr-10px"
class="is-hover mr-10px cursor-pointer"
:icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
color="var(--el-color-info)"
hover-color="var(--el-color-primary)"
@click="toggleFull"
/>
<Icon
class="cursor-pointer is-hover"
class="is-hover cursor-pointer"
icon="ep:close"
hover-color="var(--el-color-primary)"
color="var(--el-color-info)"

View File

@ -180,12 +180,12 @@ defineExpose({
</script>
<template>
<div class="border-1 border-solid border-[var(--el-border-color)] z-99">
<div class="z-99 border-1 border-[var(--el-border-color)] border-solid">
<!-- 工具栏 -->
<Toolbar
:editor="editorRef"
:editorId="editorId"
class="border-0 b-b-1 border-solid border-[var(--el-border-color)]"
class="border-0 b-b-1 border-[var(--el-border-color)] border-solid"
/>
<!-- 编辑器 -->
<Editor

View File

@ -203,7 +203,7 @@ export default defineComponent({
icon="ep:warning"
size={16}
color="var(--el-color-primary)"
class="ml-2px relative top-1px"
class="relative top-1px ml-2px"
></Icon>
)
}}

View File

@ -32,6 +32,7 @@ const getIconifyStyle = computed(() => {
const { color, size } = props
return {
fontSize: `${size}px`,
height: '1em',
color
}
})

View File

@ -128,7 +128,7 @@ watch(
>
<template #reference>
<div
class="w-40px h-32px cursor-pointer flex justify-center items-center"
class="h-32px w-40px flex cursor-pointer items-center justify-center"
@click="visible = !visible"
>
<Icon :icon="currentActiveType + icon" />
@ -147,13 +147,13 @@ watch(
>
<ElDivider border-style="dashed" class="tab-divider" />
<ElScrollbar height="220px">
<ul class="flex flex-wrap px-2 ml-2">
<ul class="ml-2 flex flex-wrap px-2">
<li
v-for="(item, key) in pageList"
:key="key"
:style="iconItemStyle(item)"
:title="item"
class="icon-item p-2 w-1/10 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-solid"
class="icon-item mr-2 mt-1 w-1/10 flex cursor-pointer items-center justify-center border border-solid p-2"
@click="onChangeIcon(item)"
>
<Icon :icon="currentActiveType + item" />
@ -169,7 +169,7 @@ watch(
:page-size="pageSize"
:total="iconCount"
background
class="flex items-center justify-center h-10"
class="h-10 flex items-center justify-center"
layout="prev, pager, next"
small
@current-change="onCurrentChange"

View File

@ -40,7 +40,7 @@ const keyClick = (key: string) => {
<span :class="[`${prefixCls}__title`, 'pl-5px text-16px font-bold']">{{ title }}</span>
</div>
<div :class="`${prefixCls}__content`">
<p v-for="(item, $index) in schema" :key="$index" class="text-14px mt-15px">
<p v-for="(item, $index) in schema" :key="$index" class="mt-15px text-14px">
<Highlight
:color="highlightColor"
:keys="typeof item === 'string' ? [] : item.keys"

View File

@ -67,7 +67,7 @@ const getIconName = computed(() => (unref(textType) === 'password' ? 'ep:hide' :
<div
v-if="strength"
:class="`${prefixCls}__bar`"
class="relative h-6px mt-10px mb-6px mr-auto ml-auto"
class="relative mb-6px ml-auto mr-auto mt-10px h-6px"
>
<div :class="`${prefixCls}__bar--fill`" :data-score="getPasswordStrength"></div>
</div>

View File

@ -9,7 +9,7 @@
:pager-count="pagerCount"
:total="total"
:small="isSmall"
class="float-right mt-15px mb-15px"
class="float-right mb-15px mt-15px"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"

View File

@ -227,10 +227,10 @@ const disabledClick = () => {
<div
v-if="disabled"
:class="`${prefixCls}--disabled`"
class="absolute top-0 left-0 flex w-full h-full items-center justify-center"
class="absolute left-0 top-0 h-full w-full flex items-center justify-center"
@click="disabledClick"
>
<div class="absolute top-[50%] left-[50%] font-bold">
<div class="absolute left-[50%] top-[50%] font-bold">
<Icon :size="30" color="var(--el-color-primary)" icon="ep:refresh-right" />
<div>{{ disabledText }}</div>
</div>

View File

@ -289,7 +289,7 @@ export default defineComponent({
<ElPagination
v-model:pageSize={pageSizeRef.value}
v-model:currentPage={currentPageRef.value}
class="float-right mt-15px mb-15px"
class="float-right mb-15px mt-15px"
{...unref(pagination)}
></ElPagination>
) : undefined}

View File

@ -12,6 +12,6 @@ defineProps({
<template>
<span>{{ titel }}</span>
<ElTooltip :content="message" placement="top">
<Icon :icon="icon" class="ml-1px relative top-1px" />
<Icon :icon="icon" class="relative top-1px ml-1px" />
</ElTooltip>
</template>

View File

@ -11,6 +11,7 @@ import BpmnViewer from 'bpmn-js/lib/Viewer'
import DefaultEmptyXML from './plugins/defaultEmpty'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import { isEmpty } from '@/utils/is'
defineOptions({ name: 'MyProcessViewer' })
@ -85,6 +86,7 @@ const createNewDiagram = async (xml) => {
// console.error(`[Process Designer Warn]: ${e?.message || e}`);
}
}
/* 高亮流程图 */
// TODO endActivity https://www.jdon.com/workflow/multi-events.html
const highlightDiagram = async () => {
@ -97,6 +99,9 @@ const highlightDiagram = async () => {
let canvas = bpmnModeler.get('canvas')
let todoActivity: any = activityList.find((m: any) => !m.endTime) //
let endActivity: any = activityList[activityList.length - 1] //
let findProcessTask = false //
// key taskList Hover
let removeTaskDefinitionKeyList = []
// debugger
bpmnModeler.getDefinitions().rootElements[0].flowElements?.forEach((n: any) => {
let activity: any = activityList.find((m: any) => m.key === n.id) //
@ -110,9 +115,17 @@ const highlightDiagram = async () => {
if (!task) {
return
}
//
if (findProcessTask) {
removeTaskDefinitionKeyList.push(n.id)
return
}
//
canvas.addMarker(n.id, getResultCss(task.result))
//
if (task.result === 1) {
findProcessTask = true
}
// 线
if (task.result !== 2) {
return
@ -212,6 +225,11 @@ const highlightDiagram = async () => {
}
}
})
if (!isEmpty(removeTaskDefinitionKeyList)) {
taskList.value = taskList.value.filter(
(item) => !removeTaskDefinitionKeyList.includes(item.definitionKey)
)
}
}
const getActivityHighlightCss = (activity) => {
return activity.endTime ? 'highlight' : 'highlight-todo'
@ -229,6 +247,9 @@ const getResultCss = (result) => {
} else if (result === 4) {
//
return 'highlight-cancel'
} else if (result === 5) {
// 退
return 'highlight-return'
}
return ''
}
@ -273,9 +294,9 @@ const elementHover = (element) => {
console.log(element.value, 'element.value')
const activity = activityLists.value.find((m) => m.key === element.value.id)
console.log(activity, 'activityactivityactivityactivity')
// if (!activity) {
// return
// }
if (!activity) {
return
}
if (!elementOverlayIds.value[element.value.id] && element.value.type !== 'bpmn:Process') {
let html = `<div class="element-overlays">
<p>Elemet id: ${element.value.id}</p>
@ -564,6 +585,45 @@ watch(
stroke: grey !important;
}
/** 回退 */
.highlight-return.djs-shape .djs-visual > :nth-child(1) {
fill: #e6a23c !important;
stroke: #e6a23c !important;
fill-opacity: 0.2 !important;
}
.highlight-return.djs-shape .djs-visual > :nth-child(2) {
fill: #e6a23c !important;
}
.highlight-return.djs-shape .djs-visual > path {
fill: #e6a23c !important;
fill-opacity: 0.2 !important;
stroke: #e6a23c !important;
}
.highlight-return.djs-connection > .djs-visual > path {
stroke: #e6a23c !important;
}
.highlight-return:not(.djs-connection) .djs-visual > :nth-child(1) {
fill: #e6a23c !important; /* color elements as green */
}
:deep(.highlight-return.djs-shape .djs-visual > :nth-child(1)) {
fill: #e6a23c !important;
stroke: #e6a23c !important;
fill-opacity: 0.2 !important;
}
:deep(.highlight-return.djs-shape .djs-visual > :nth-child(2)) {
fill: #e6a23c !important;
}
:deep(.highlight-return.djs-shape .djs-visual > path) {
fill: #e6a23c !important;
fill-opacity: 0.2 !important;
stroke: #e6a23c !important;
}
:deep(.highlight-return.djs-connection > .djs-visual > path) {
stroke: #e6a23c !important;
}
.element-overlays {
width: 200px;
padding: 8px;

View File

@ -15,7 +15,7 @@
<!--字段列表-->
<div class="element-property list-property">
<el-divider><Icon icon="ep:coin" /> 表单字段</el-divider>
<el-table :data="fieldList" max-height="240" border fit>
<el-table :data="fieldList" max-height="240" fit border>
<el-table-column label="序号" type="index" width="50px" />
<el-table-column label="字段名称" prop="label" min-width="80px" show-overflow-tooltip />
<el-table-column
@ -97,7 +97,7 @@
>添加枚举值</el-button
>
</p>
<el-table :data="fieldEnumList" key="enum-table" max-height="240" border fit>
<el-table :data="fieldEnumList" key="enum-table" max-height="240" fit border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="枚举值编号" prop="id" min-width="100px" show-overflow-tooltip />
<el-table-column label="枚举值名称" prop="name" min-width="100px" show-overflow-tooltip />
@ -130,7 +130,7 @@
>添加约束</el-button
>
</p>
<el-table :data="fieldConstraintsList" key="validation-table" max-height="240" border fit>
<el-table :data="fieldConstraintsList" key="validation-table" max-height="240" fit border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="约束名称" prop="name" min-width="100px" show-overflow-tooltip />
<el-table-column label="约束配置" prop="config" min-width="100px" show-overflow-tooltip />
@ -162,7 +162,7 @@
>添加属性</el-button
>
</p>
<el-table :data="fieldPropertiesList" key="property-table" max-height="240" border fit>
<el-table :data="fieldPropertiesList" key="property-table" max-height="240" fit border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="属性编号" prop="id" min-width="100px" show-overflow-tooltip />
<el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />

View File

@ -139,8 +139,8 @@
:data="fieldsListOfListener"
size="small"
max-height="240"
border
fit
border
style="flex: none"
>
<el-table-column label="序号" width="50px" type="index" />

View File

@ -184,8 +184,8 @@
:data="fieldsListOfListener"
size="small"
max-height="240"
border
fit
border
style="flex: none"
>
<el-table-column label="序号" width="50px" type="index" />

View File

@ -1,6 +1,6 @@
<template>
<div class="panel-tab__content">
<el-table :data="elementPropertyList" max-height="240" border fit>
<el-table :data="elementPropertyList" max-height="240" fit border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="属性名" prop="name" min-width="100px" show-overflow-tooltip />
<el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />

View File

@ -9,7 +9,7 @@ import { TableColumn } from '@/types/table'
import { DescriptionsSchema } from '@/types/descriptions'
import { ComponentOptions, ComponentProps } from '@/types/components'
import { DictTag } from '@/components/DictTag'
import { cloneDeep } from 'lodash-es'
import { cloneDeep, merge } from 'lodash-es'
export type CrudSchema = Omit<TableColumn, 'children'> & {
isSearch?: boolean // 是否在查询显示
@ -117,14 +117,18 @@ const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): F
}
if (!schemaItem.search?.component) component = 'Select'
}
const searchSchemaItem = {
// 默认为 input
component: component,
componentProps: comonentProps,
...schemaItem.search,
field: schemaItem.field,
label: schemaItem.search?.label || schemaItem.label
}
// updated by AKing: 解决了当使用默认的dict选项时form中事件不能触发的问题
const searchSchemaItem = merge(
{
// 默认为 input
component,
...schemaItem.search,
field: schemaItem.field,
label: schemaItem.search?.label || schemaItem.label
},
{ componentProps: comonentProps }
)
if (searchSchemaItem.api) {
searchRequestTask.push(async () => {
const res = await (searchSchemaItem.api as () => AxiosPromise)()
@ -224,15 +228,19 @@ const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): For
}
if (!(schemaItem.form && schemaItem.form.component)) component = 'Select'
}
const formSchemaItem = {
// 默认为 input
component: component,
componentProps: comonentProps,
value: defaultValue,
...schemaItem.form,
field: schemaItem.field,
label: schemaItem.form?.label || schemaItem.label
}
// updated by AKing: 解决了当使用默认的dict选项时form中事件不能触发的问题
const formSchemaItem = merge(
{
// 默认为 input
component,
value: defaultValue,
...schemaItem.form,
field: schemaItem.field,
label: schemaItem.form?.label || schemaItem.label
},
{ componentProps: comonentProps }
)
if (formSchemaItem.api) {
formRequestTask.push(async () => {

View File

@ -50,7 +50,7 @@ export default defineComponent({
<section class={[prefixCls, `${prefixCls}__${layout.value}`, 'w-[100%] h-[100%] relative']}>
{mobile.value && !collapse.value ? (
<div
class="absolute top-0 left-0 w-full h-full opacity-30 z-99 bg-[var(--el-color-black)]"
class="absolute left-0 top-0 z-99 h-full w-full bg-[var(--el-color-black)] opacity-30"
onClick={handleClickOutside}
></div>
) : undefined}

View File

@ -52,10 +52,10 @@ export default defineComponent({
return (
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
{meta?.icon && breadcrumbIcon.value ? (
<>
<div class="flex items-center">
<Icon icon={meta.icon} class="mr-[2px]" svgClass="inline-block"></Icon>
{t(v?.meta?.title)}
</>
</div>
) : (
t(v?.meta?.title)
)}
@ -114,9 +114,10 @@ $prefix-cls: #{$elNamespace}-breadcrumb;
}
}
}
:deep(&__item):last-child {
.#{$prefix-cls}__inner {
display: flex;
align-items: center;
color: var(--el-text-color-placeholder);
&:hover {

View File

@ -17,7 +17,7 @@ const title = computed(() => appStore.getTitle)
<template>
<div
:class="prefixCls"
class="text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-contnet-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] 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)]"
>
<p style="font-size: 14px">Copyright ©2022-{{ title }}</p>
</div>

View File

@ -67,7 +67,7 @@ watch(
to="/"
>
<img
class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
class="h-[calc(var(--logo-height)-10px)] w-[calc(var(--logo-height)-10px)]"
src="@/assets/imgs/logo.png"
/>
<div

View File

@ -172,7 +172,7 @@ $prefix-cls: #{$namespace}-menu;
.#{$elNamespace}-menu-item.is-active {
position: relative;
&:after {
&::after {
@extend .is-active--after;
}
}
@ -195,7 +195,7 @@ $prefix-cls: #{$namespace}-menu;
position: relative;
background-color: var(--left-menu-collapse-bg-active-color) !important;
&:after {
&::after {
@extend .is-active--after;
}
}
@ -226,7 +226,7 @@ $prefix-cls: #{$namespace}-menu;
.#{$elNamespace}-menu-item.is-active {
position: relative;
&:after {
&::after {
display: none !important;
}
}
@ -282,7 +282,7 @@ $prefix-cls: #{$namespace}-menu-popper;
background-color: var(--left-menu-bg-active-color) !important;
}
&:after {
&::after {
@extend .is-active--after;
}
}

View File

@ -200,7 +200,7 @@ const clear = () => {
<template>
<div
:class="prefixCls"
class="fixed top-[45%] right-0 w-40px h-40px text-center leading-40px bg-[var(--el-color-primary)] cursor-pointer"
class="fixed right-0 top-[45%] h-40px w-40px cursor-pointer bg-[var(--el-color-primary)] text-center leading-40px"
@click="drawer = true"
>
<Icon color="#fff" icon="ep:setting" />

View File

@ -48,7 +48,7 @@ watch(
:style="{
background: item
}"
class="w-20px h-20px cursor-pointer rounded-2px border-solid border-gray-300 border-2px text-center leading-20px mb-5px"
class="mb-5px h-20px w-20px cursor-pointer border-2px border-gray-300 rounded-2px border-solid text-center leading-20px"
@click="colorVal = item"
>
<Icon v-if="colorVal === item" :size="16" color="#fff" icon="ep:check" />

View File

@ -141,84 +141,84 @@ watch(
<template>
<div :class="prefixCls">
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.breadcrumb') }}</span>
<ElSwitch v-model="breadcrumb" @change="breadcrumbChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.breadcrumbIcon') }}</span>
<ElSwitch v-model="breadcrumbIcon" @change="breadcrumbIconChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.hamburgerIcon') }}</span>
<ElSwitch v-model="hamburger" @change="hamburgerChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.screenfullIcon') }}</span>
<ElSwitch v-model="screenfull" @change="screenfullChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.sizeIcon') }}</span>
<ElSwitch v-model="size" @change="sizeChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.localeIcon') }}</span>
<ElSwitch v-model="locale" @change="localeChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.messageIcon') }}</span>
<ElSwitch v-model="message" @change="messageChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.tagsView') }}</span>
<ElSwitch v-model="tagsView" @change="tagsViewChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.tagsViewIcon') }}</span>
<ElSwitch v-model="tagsViewIcon" @change="tagsViewIconChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.logo') }}</span>
<ElSwitch v-model="logo" @change="logoChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.uniqueOpened') }}</span>
<ElSwitch v-model="uniqueOpened" @change="uniqueOpenedChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.fixedHeader') }}</span>
<ElSwitch v-model="fixedHeader" @change="fixedHeaderChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.footer') }}</span>
<ElSwitch v-model="footer" @change="footerChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.greyMode') }}</span>
<ElSwitch v-model="greyMode" @change="greyModeChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('setting.fixedMenu') }}</span>
<ElSwitch v-model="fixedMenu" @change="fixedMenuChange" />
</div>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<span class="text-14px">{{ t('watermark.watermark') }}</span>
<ElInput v-model="water" class="w-20 right-1" @change="setWater()" />
<ElInput v-model="water" class="right-1 w-20" @change="setWater()" />
</div>
</div>
</template>

View File

@ -55,7 +55,7 @@ const layout = computed(() => appStore.getLayout)
]"
@click="appStore.setLayout('cutMenu')"
>
<div class="absolute h-full w-[33%] top-0 left-[10%] bg-gray-200"></div>
<div class="absolute left-[10%] top-0 h-full w-[33%] bg-gray-200"></div>
</div>
</div>
</template>

View File

@ -176,7 +176,7 @@ export default defineComponent({
<Icon icon={item?.meta?.icon}></Icon>
</div>
{!unref(showTitle) ? undefined : (
<p class="break-words mt-5px px-2px">{t(item.meta?.title)}</p>
<p class="mt-5px break-words px-2px">{t(item.meta?.title)}</p>
)}
</div>
)

View File

@ -263,11 +263,11 @@ watch(
<div
:id="prefixCls"
:class="prefixCls"
class="flex w-full relative bg-[#fff] dark:bg-[var(--el-bg-color)]"
class="relative w-full flex bg-[#fff] dark:bg-[var(--el-bg-color)]"
>
<span
:class="`${prefixCls}__tool ${prefixCls}__tool--first`"
class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer"
class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
@click="move(-200)"
>
<Icon
@ -276,9 +276,9 @@ watch(
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
/>
</span>
<div class="overflow-hidden flex-1">
<div class="flex-1 overflow-hidden">
<ElScrollbar ref="scrollbarRef" class="h-full" @scroll="scroll">
<div class="flex h-full">
<div class="h-full flex">
<ContextMenu
:ref="itemRefs.set"
:schema="[
@ -354,7 +354,7 @@ watch(
<router-link :ref="tagLinksRefs.set" :to="{ ...item }" custom v-slot="{ navigate }">
<div
@click="navigate"
class="h-full flex justify-center items-center whitespace-nowrap pl-15px"
class="h-full flex items-center justify-center whitespace-nowrap pl-15px"
>
<Icon
v-if="
@ -384,7 +384,7 @@ watch(
</div>
<span
:class="`${prefixCls}__tool`"
class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer"
class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
@click="move(200)"
>
<Icon
@ -395,7 +395,7 @@ watch(
</span>
<span
:class="`${prefixCls}__tool`"
class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer"
class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
@click="refreshSelectedTag(selectedTag)"
>
<Icon
@ -460,7 +460,7 @@ watch(
>
<span
:class="`${prefixCls}__tool`"
class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer block"
class="block h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
>
<Icon
icon="ep:menu"

View File

@ -53,8 +53,8 @@ const toDocument = () => {
<template>
<ElDropdown class="custom-hover" :class="prefixCls" trigger="click">
<div class="flex items-center">
<img :src="avatar" alt="" class="w-[calc(var(--logo-height)-25px)] rounded-[50%]" />
<span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">
<ElAvatar :src="avatar" alt="" class="w-[calc(var(--logo-height)-25px)] rounded-[50%]" />
<span class="pl-[5px] text-14px text-[var(--top-header-text-color)] <lg:hidden">
{{ userName }}
</span>
</div>

View File

@ -107,7 +107,7 @@ export const useRenderLayout = () => {
></ToolHeader>
{tagsView.value ? (
<TagsView class="layout-border__bottom layout-border__top"></TagsView>
<TagsView class="layout-border__top layout-border__bottom"></TagsView>
) : undefined}
</div>
@ -121,13 +121,13 @@ export const useRenderLayout = () => {
const renderTopLeft = () => {
return (
<>
<div class="flex items-center bg-[var(--top-header-bg-color)] relative layout-border__bottom dark:bg-[var(--el-bg-color)]">
<div class="relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom dark:bg-[var(--el-bg-color)]">
{logo.value ? <Logo class="custom-hover"></Logo> : undefined}
<ToolHeader class="flex-1"></ToolHeader>
</div>
<div class="absolute top-[var(--logo-height)+1px] left-0 w-full h-[calc(100%-1px-var(--logo-height))] flex">
<Menu class="!h-full relative layout-border__right"></Menu>
<div class="absolute left-0 top-[var(--logo-height)+1px] h-[calc(100%-1px-var(--logo-height))] w-full flex">
<Menu class="relative layout-border__right !h-full"></Menu>
<div
class={[
`${prefixCls}-content`,
@ -187,7 +187,7 @@ export const useRenderLayout = () => {
]}
>
{logo.value ? <Logo class="custom-hover"></Logo> : undefined}
<Menu class="flex-1 px-10px h-[var(--top-tool-height)]"></Menu>
<Menu class="h-[var(--top-tool-height)] flex-1 px-10px"></Menu>
<ToolHeader></ToolHeader>
</div>
<div
@ -233,12 +233,12 @@ export const useRenderLayout = () => {
const renderCutMenu = () => {
return (
<>
<div class="flex items-center bg-[var(--top-header-bg-color)] relative layout-border__bottom">
<div class="relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom">
{logo.value ? <Logo class="custom-hover !pr-15px"></Logo> : undefined}
<ToolHeader class="flex-1"></ToolHeader>
</div>
<div class="absolute top-[var(--logo-height)] left-0 w-[calc(100%-2px)] h-[calc(100%-var(--logo-height))] flex">
<div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-[calc(100%-2px)] flex">
<TabMenu></TabMenu>
<div
class={[

View File

@ -331,9 +331,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
]
},
{
path: '/product',
path: '/mall/product', // 商品中心
component: Layout,
name: 'Product',
meta: {
hidden: true
},
@ -347,12 +346,12 @@ const remainingRouter: AppRouteRecordRaw[] = [
hidden: true,
canTo: true,
icon: 'ep:edit',
title: '添加商品',
activeMenu: '/product/product-spu'
title: '商品添加',
activeMenu: '/mall/product/spu'
}
},
{
path: 'spu/edit/:spuId(\\d+)',
path: 'spu/edit/:id(\\d+)',
component: () => import('@/views/mall/product/spu/form/index.vue'),
name: 'ProductSpuEdit',
meta: {
@ -360,12 +359,12 @@ const remainingRouter: AppRouteRecordRaw[] = [
hidden: true,
canTo: true,
icon: 'ep:edit',
title: '编辑商品',
activeMenu: '/product/product-spu'
title: '商品编辑',
activeMenu: '/mall/product/spu'
}
},
{
path: 'spu/detail/:spuId(\\d+)',
path: 'spu/detail/:id(\\d+)',
component: () => import('@/views/mall/product/spu/form/index.vue'),
name: 'ProductSpuDetail',
meta: {
@ -374,7 +373,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
canTo: true,
icon: 'ep:view',
title: '商品详情',
activeMenu: '/product/product-spu'
activeMenu: '/mall/product/spu'
}
},
{
@ -393,24 +392,23 @@ const remainingRouter: AppRouteRecordRaw[] = [
]
},
{
path: '/trade',
path: '/mall/trade', // 交易中心
component: Layout,
name: 'Order',
meta: {
hidden: true
},
children: [
{
path: 'order/detail/:orderId(\\d+)',
path: 'order/detail/:id(\\d+)',
component: () => import('@/views/mall/trade/order/detail/index.vue'),
name: 'TradeOrderDetail',
meta: { title: '订单详情', icon: '', activeMenu: '/trade/trade/order' }
meta: { title: '订单详情', icon: 'ep:view', activeMenu: '/mall/trade/order' }
},
{
path: 'after-sale/detail/:orderId(\\d+)',
path: 'after-sale/detail/:id(\\d+)',
component: () => import('@/views/mall/trade/afterSale/detail/index.vue'),
name: 'TradeAfterSaleDetail',
meta: { title: '退款详情', icon: '', activeMenu: '/trade/trade/after-sale' }
meta: { title: '退款详情', icon: 'ep:view', activeMenu: '/mall/trade/after-sale' }
}
]
},

View File

@ -48,7 +48,7 @@
--app-content-padding: 20px;
--app-contnet-bg-color: #f5f7f9;
--app-content-bg-color: #f5f7f9;
--app-footer-height: 50px;

View File

@ -4,12 +4,20 @@
*
*/
// ========== COMMON 模块 ==========
// 全局通用状态枚举
export const CommonStatusEnum = {
ENABLE: 0, // 开启
DISABLE: 1 // 禁用
}
// 全局用户类型枚举
export const UserTypeEnum = {
MEMBER: 1, // 会员
ADMIN: 2 // 管理员
}
// ========== SYSTEM 模块 ==========
/**
*
*/
@ -38,6 +46,25 @@ export const SystemDataScopeEnum = {
DEPT_SELF: 5 // 仅本人数据权限
}
/**
*
*/
export const SystemUserSocialTypeEnum = {
DINGTALK: {
title: '钉钉',
type: 20,
source: 'dingtalk',
img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png'
},
WECHAT_ENTERPRISE: {
title: '企业微信',
type: 30,
source: 'wechat_enterprise',
img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png'
}
}
// ========== INFRA 模块 ==========
/**
*
*/
@ -65,24 +92,7 @@ export const InfraApiErrorLogProcessStatusEnum = {
IGNORE: 2 // 已忽略
}
/**
*
*/
export const SystemUserSocialTypeEnum = {
DINGTALK: {
title: '钉钉',
type: 20,
source: 'dingtalk',
img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png'
},
WECHAT_ENTERPRISE: {
title: '企业微信',
type: 30,
source: 'wechat_enterprise',
img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png'
}
}
// ========== PAY 模块 ==========
/**
*
*/
@ -177,6 +187,7 @@ export const PayOrderStatusEnum = {
}
}
// ========== MALL - 商品模块 ==========
/**
* SPU
*/
@ -195,6 +206,7 @@ export const ProductSpuStatusEnum = {
}
}
// ========== MALL - 营销模块 ==========
/**
*
*/
@ -273,17 +285,22 @@ export const PromotionDiscountTypeEnum = {
}
}
// ========== MALL - 交易模块 ==========
/**
*
*/
export const BrokerageBindModeEnum = {
ANYTIME: {
mode: 0,
name: '没有推广人'
mode: 1,
name: '首次绑定'
},
REGISTER: {
mode: 1,
name: '新用户'
mode: 2,
name: '注册绑定'
},
OVERRIDE: {
mode: 3,
name: '覆盖绑定'
}
}
/**
@ -291,11 +308,11 @@ export const BrokerageBindModeEnum = {
*/
export const BrokerageEnabledConditionEnum = {
ALL: {
condition: 0,
condition: 1,
name: '人人分销'
},
ADMIN: {
condition: 1,
condition: 2,
name: '指定分销'
}
}
@ -358,3 +375,42 @@ export const BrokerageWithdrawTypeEnum = {
name: '支付宝'
}
}
/**
*
*/
export const DeliveryTypeEnum = {
EXPRESS: {
type: 1,
name: '快递发货'
},
PICK_UP: {
type: 2,
name: '到店自提'
}
}
/**
* -
*/
export const TradeOrderStatusEnum = {
UNPAID: {
status: 0,
name: '待支付'
},
UNDELIVERED: {
status: 10,
name: '待发货'
},
DELIVERED: {
status: 20,
name: '已发货'
},
COMPLETED: {
status: 30,
name: '已完成'
},
CANCELED: {
status: 40,
name: '已取消'
}
}

View File

@ -168,7 +168,7 @@ export enum DICT_TYPE {
BROKERAGE_ENABLED_CONDITION = 'brokerage_enabled_condition', // 分佣模式
BROKERAGE_BIND_MODE = 'brokerage_bind_mode', // 分销关系绑定模式
BROKERAGE_BANK_NAME = 'brokerage_bank_name', // 佣金提现银行
BROKERAGE_WITHDRAW_TYPE = 'brokerage_withdraw_type', // 佣金冻结时间
BROKERAGE_WITHDRAW_TYPE = 'brokerage_withdraw_type', // 佣金提现类型
BROKERAGE_RECORD_BIZ_TYPE = 'brokerage_record_biz_type', // 佣金业务类型
BROKERAGE_RECORD_STATUS = 'brokerage_record_status', // 佣金状态
BROKERAGE_WITHDRAW_STATUS = 'brokerage_withdraw_status', // 佣金提现状态
@ -180,5 +180,6 @@ export enum DICT_TYPE {
PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态
PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式
PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态
PROMOTION_CONDITION_TYPE = 'promotion_condition_type' // 营销的条件类型枚举
PROMOTION_CONDITION_TYPE = 'promotion_condition_type', // 营销的条件类型枚举
PROMOTION_BARGAIN_RECORD_STATUS = 'promotion_bargain_record_status' // 砍价记录的状态
}

View File

@ -11,7 +11,7 @@ import dayjs from 'dayjs'
* @description format + + "YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
* @returns
*/
export function formatDate(date: Date | number, format?: string): string {
export function formatDate(date: dayjs.ConfigType, format?: string): string {
// 日期不存在,则返回空
if (!date) {
return ''
@ -221,3 +221,68 @@ export function convertDate(param: Date | string) {
}
return param
}
/**
* ,
* @param a A
* @param b B
*/
export function isSameDay(a: dayjs.ConfigType, b: dayjs.ConfigType): boolean {
if (!a || !b) return false
const aa = dayjs(a)
const bb = dayjs(b)
return aa.year() == bb.year() && aa.month() == bb.month() && aa.day() == bb.day()
}
/**
*
* @param date
* @param days
*/
export function getDayRange(
date: dayjs.ConfigType,
days: number
): [dayjs.ConfigType, dayjs.ConfigType] {
const day = dayjs(date).add(days, 'd')
return getDateRange(day, day)
}
/**
* 7
*/
export function getLast7Days(): [dayjs.ConfigType, dayjs.ConfigType] {
const lastWeekDay = dayjs().subtract(7, 'd')
const yesterday = dayjs().subtract(1, 'd')
return getDateRange(lastWeekDay, yesterday)
}
/**
* 30
*/
export function getLast30Days(): [dayjs.ConfigType, dayjs.ConfigType] {
const lastMonthDay = dayjs().subtract(30, 'd')
const yesterday = dayjs().subtract(1, 'd')
return getDateRange(lastMonthDay, yesterday)
}
/**
* 1
*/
export function getLast1Year(): [dayjs.ConfigType, dayjs.ConfigType] {
const lastYearDay = dayjs().subtract(1, 'y')
const yesterday = dayjs().subtract(1, 'd')
return getDateRange(lastYearDay, yesterday)
}
/**
*
* @param beginDate
* @param endDate
*/
export function getDateRange(
beginDate: dayjs.ConfigType,
endDate: dayjs.ConfigType
): [dayjs.ConfigType, dayjs.ConfigType] {
return [dayjs(beginDate).startOf('d'), dayjs(endDate).endOf('d')]
}

View File

@ -230,6 +230,7 @@ export const yuanToFen = (amount: string | number): number => {
/**
*
*/
export const fenToYuan = (amount: string | number): number => {
return Number((Number(amount) / 100).toFixed(2))
export const fenToYuan = (price: string | number): number => {
price = Number(price)
return (price / 100.0).toFixed(2)
}

View File

@ -13,7 +13,8 @@ export const defaultProps = {
children: 'children',
label: 'name',
value: 'id',
isLeaf: 'leaf'
isLeaf: 'leaf',
emitPath: false // 用于 cascader 组件:在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false则只返回该节点的值
}
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
@ -377,10 +378,10 @@ export const treeToString = (tree: any[], nodeId) => {
function performAThoroughValidation(arr) {
for (const item of arr) {
if (item.id === nodeId) {
str += `/${item.name}`
str += ` / ${item.name}`
return true
} else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
str += `/${item.name}`
str += ` / ${item.name}`
if (performAThoroughValidation(item.children)) {
return true
}

View File

@ -5,7 +5,7 @@
<el-row :gutter="20" justify="space-between">
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex items-center">
<img :src="avatar" alt="" class="w-70px h-70px rounded-[50%] mr-20px" />
<img :src="avatar" alt="" class="mr-20px h-70px w-70px rounded-[50%]" />
<div>
<div class="text-20px">
{{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
@ -17,9 +17,9 @@
</div>
</el-col>
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex h-70px items-center justify-end lt-sm:mt-10px">
<div class="h-70px flex items-center justify-end lt-sm:mt-10px">
<div class="px-8px text-right">
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</div>
<div class="mb-20px text-14px text-gray-400">{{ t('workplace.project') }}</div>
<CountTo
class="text-20px"
:start-val="0"
@ -29,7 +29,7 @@
</div>
<el-divider direction="vertical" />
<div class="px-8px text-right">
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.toDo') }}</div>
<div class="mb-20px text-14px text-gray-400">{{ t('workplace.toDo') }}</div>
<CountTo
class="text-20px"
:start-val="0"
@ -39,7 +39,7 @@
</div>
<el-divider direction="vertical" border-style="dashed" />
<div class="px-8px text-right">
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.access') }}</div>
<div class="mb-20px text-14px text-gray-400">{{ t('workplace.access') }}</div>
<CountTo
class="text-20px"
:start-val="0"
@ -58,7 +58,7 @@
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-10px">
<el-card shadow="never">
<template #header>
<div class="flex justify-between h-3">
<div class="h-3 flex justify-between">
<span>{{ t('workplace.project') }}</span>
<el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
</div>
@ -80,7 +80,7 @@
<span class="text-16px">{{ item.name }}</span>
</div>
<div class="mt-15px text-14px text-gray-400">{{ t(item.message) }}</div>
<div class="mt-20px text-12px text-gray-400 flex justify-between">
<div class="mt-20px flex justify-between text-12px text-gray-400">
<span>{{ item.personal }}</span>
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
</div>
@ -114,7 +114,7 @@
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-10px">
<el-card shadow="never">
<template #header>
<div class="flex justify-between h-3">
<div class="h-3 flex justify-between">
<span>{{ t('workplace.shortcutOperation') }}</span>
</div>
</template>
@ -133,7 +133,7 @@
</el-card>
<el-card shadow="never" class="mt-10px">
<template #header>
<div class="flex justify-between h-3">
<div class="h-3 flex justify-between">
<span>{{ t('workplace.notice') }}</span>
<el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
</div>
@ -141,7 +141,7 @@
<el-skeleton :loading="loading" animated>
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
<div class="flex items-center">
<img :src="avatar" alt="" class="w-35px h-35px rounded-[50%] mr-20px" />
<img :src="avatar" alt="" class="mr-20px h-35px w-35px rounded-[50%]" />
<div>
<div class="text-14px">
<Highlight :keys="item.keys.map((v) => t(v))">

View File

@ -20,7 +20,7 @@
:duration="2600"
:end-val="102400"
:start-val="0"
class="text-20px font-700 text-right"
class="text-right text-20px font-700"
/>
</div>
</div>
@ -49,7 +49,7 @@
:duration="2600"
:end-val="81212"
:start-val="0"
class="text-20px font-700 text-right"
class="text-right text-20px font-700"
/>
</div>
</div>
@ -78,7 +78,7 @@
:duration="2600"
:end-val="9280"
:start-val="0"
class="text-20px font-700 text-right"
class="text-right text-20px font-700"
/>
</div>
</div>
@ -107,7 +107,7 @@
:duration="2600"
:end-val="13600"
:start-val="0"
class="text-20px font-700 text-right"
class="text-right text-20px font-700"
/>
</div>
</div>

View File

@ -1,19 +1,19 @@
<template>
<div
:class="prefixCls"
class="h-[100%] relative lt-xl:bg-[var(--login-bg-color)] lt-sm:px-10px lt-xl:px-10px lt-md:px-10px"
class="relative h-[100%] lt-xl:bg-[var(--login-bg-color)] lt-md:px-10px lt-sm:px-10px lt-xl:px-10px"
>
<div class="relative h-full flex mx-auto">
<div class="relative mx-auto h-full flex">
<div
:class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden`"
>
<!-- 左上角的 logo + 系统标题 -->
<div class="flex items-center relative text-white">
<img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
<div class="relative flex items-center text-white">
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
</div>
<!-- 左边的背景图 + 欢迎语 -->
<div class="flex justify-center items-center h-[calc(100%-60px)]">
<div class="h-[calc(100%-60px)] flex items-center justify-center">
<TransitionGroup
appear
enter-active-class="animate__animated animate__bounceInLeft"
@ -21,41 +21,41 @@
>
<img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" />
<div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div>
<div key="3" class="mt-5 font-normal text-white text-14px">
<div key="3" class="mt-5 text-14px font-normal text-white">
{{ t('login.message') }}
</div>
</TransitionGroup>
</div>
</div>
<div class="flex-1 p-30px lt-sm:p-10px dark:bg-[var(--login-bg-color)] relative">
<div class="relative flex-1 p-30px dark:bg-[var(--login-bg-color)] lt-sm:p-10px">
<!-- 右上角的主题语言选择 -->
<div
class="flex justify-between items-center 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"
>
<div class="flex items-center at-2xl:hidden at-xl:hidden">
<img alt="" class="w-48px h-48px mr-10px" 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>
</div>
<div class="flex justify-end items-center space-x-10px">
<div class="flex items-center justify-end space-x-10px">
<ThemeSwitch />
<LocaleDropdown class="lt-xl:text-white dark:text-white" />
<LocaleDropdown class="dark:text-white lt-xl:text-white" />
</div>
</div>
<!-- 右边的登录界面 -->
<Transition appear enter-active-class="animate__animated animate__bounceInRight">
<div
class="h-full flex items-center m-auto w-[100%] at-2xl:max-w-500px at-xl:max-w-500px at-md:max-w-500px at-lg:max-w-500px"
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"
>
<!-- 账号登录 -->
<LoginForm class="p-20px h-auto m-auto lt-xl:(rounded-3xl light:bg-white)" />
<LoginForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
<!-- 手机登录 -->
<MobileForm class="p-20px h-auto m-auto lt-xl:(rounded-3xl light:bg-white)" />
<MobileForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
<!-- 二维码登录 -->
<QrCodeForm class="p-20px h-auto m-auto lt-xl:(rounded-3xl light:bg-white)" />
<QrCodeForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
<!-- 注册 -->
<RegisterForm class="p-20px h-auto m-auto lt-xl:(rounded-3xl light:bg-white)" />
<RegisterForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
<!-- 三方登录 -->
<SSOLoginVue class="p-20px h-auto m-auto lt-xl:(rounded-3xl light:bg-white)" />
<SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
</div>
</Transition>
</div>

View File

@ -112,13 +112,13 @@
<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<div class="flex justify-between w-[100%]">
<div class="w-[100%] flex justify-between">
<Icon
v-for="(item, key) in socialList"
:key="key"
:icon="item.icon"
:size="30"
class="cursor-pointer anticon"
class="anticon cursor-pointer"
color="#999"
@click="doSocialLogin(item.type)"
/>
@ -128,7 +128,7 @@
<el-divider content-position="center">萌新必读</el-divider>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<div class="flex justify-between w-[100%]">
<div class="w-[100%] flex justify-between">
<el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
<el-link href="https://doc.iocoder.cn/video/" target="_blank">🔥视频教程</el-link>
<el-link href="https://www.iocoder.cn/Interview/good-collection/" target="_blank">

View File

@ -1,5 +1,5 @@
<template>
<h2 class="mb-3 text-2xl font-bold text-center xl:text-3xl enter-x xl:text-center">
<h2 class="enter-x mb-3 text-center text-2xl font-bold xl:text-center xl:text-3xl">
{{ getFormTitle }}
</h2>
</template>

View File

@ -10,7 +10,7 @@
</el-col>
<el-divider class="enter-x">{{ t('login.qrcode') }}</el-divider>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<div class="w-[100%] mt-15px">
<div class="mt-15px w-[100%]">
<XButton :title="t('login.backLogin')" class="w-[100%]" @click="handleBackLogin()" />
</div>
</el-col>

View File

@ -29,7 +29,7 @@
@click="loginRegister()"
/>
</div>
<div class="w-[100%] mt-15px">
<div class="mt-15px w-[100%]">
<XButton :title="t('login.hasUser')" class="w-[100%]" @click="handleBackLogin()" />
</div>
</template>

View File

@ -1,6 +1,6 @@
<template>
<div class="flex">
<el-card class="w-1/3 user" shadow="hover">
<el-card class="user w-1/3" shadow="hover">
<template #header>
<div class="card-header">
<span>{{ t('profile.user.title') }}</span>
@ -8,7 +8,7 @@
</template>
<ProfileUser />
</el-card>
<el-card class="w-2/3 user ml-3" shadow="hover">
<el-card class="user ml-3 w-2/3" shadow="hover">
<template #header>
<div class="card-header">
<span>{{ t('profile.info.title') }}</span>

View File

@ -69,6 +69,9 @@ const getTimelineItemIcon = (item) => {
if (item.result === 4) {
return 'el-icon-remove-outline'
}
if (item.result === 5) {
return 'el-icon-back'
}
return ''
}
@ -86,6 +89,12 @@ const getTimelineItemType = (item) => {
if (item.result === 4) {
return 'info'
}
if (item.result === 5) {
return 'warning'
}
if (item.result === 6) {
return 'default'
}
return ''
}
</script>

View File

@ -0,0 +1,86 @@
<template>
<Dialog v-model="dialogVisible" title="委派任务" width="500">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="110px"
>
<el-form-item label="接收人" prop="delegateUserId">
<el-select v-model="formData.delegateUserId" clearable style="width: 100%">
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="委派理由" prop="reason">
<el-input v-model="formData.reason" clearable placeholder="请输入委派理由" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as TaskApi from '@/api/bpm/task'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'BpmTaskDelegateForm' })
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
id: '',
delegateUserId: undefined
})
const formRules = ref({
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }]
})
const formRef = ref() // Ref
const userList = ref<any[]>([]) //
/** 打开弹窗 */
const open = async (id: string) => {
dialogVisible.value = true
resetForm()
formData.value.id = id
//
userList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
await TaskApi.delegateTask(formData.value)
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: '',
delegateUserId: undefined
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,90 @@
<template>
<Dialog v-model="dialogVisible" title="回退" width="500">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="110px"
>
<el-form-item label="退回节点" prop="targetDefinitionKey">
<el-select v-model="formData.targetDefinitionKey" clearable style="width: 100%">
<el-option
v-for="item in returnList"
:key="item.definitionKey"
:label="item.name"
:value="item.definitionKey"
/>
</el-select>
</el-form-item>
<el-form-item label="回退理由" prop="reason">
<el-input v-model="formData.reason" clearable placeholder="请输入回退理由" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" name="TaskRollbackDialogForm" setup>
import * as TaskApi from '@/api/bpm/task'
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
id: '',
targetDefinitionKey: undefined,
reason: ''
})
const formRules = ref({
targetDefinitionKey: [{ required: true, message: '必须选择回退节点', trigger: 'change' }],
reason: [{ required: true, message: '回退理由不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const returnList = ref([])
/** 打开弹窗 */
const open = async (id: string) => {
returnList.value = await TaskApi.getReturnList({ taskId: id })
if (returnList.value.length === 0) {
message.warning('当前没有可回退的节点')
return false
}
dialogVisible.value = true
resetForm()
formData.value.id = id
}
defineExpose({ open }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
await TaskApi.returnTask(formData.value)
message.success('回退成功')
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: '',
targetDefinitionKey: undefined,
reason: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -91,6 +91,10 @@
<!-- 弹窗转派审批人 -->
<TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" />
<!-- 弹窗回退节点 -->
<TaskReturnDialog ref="taskReturnDialogRef" @success="getDetail" />
<!-- 委派将任务委派给别人处理处理完成后会重新回到原审批人手中-->
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
</ContentWrap>
</template>
<script lang="ts" setup>
@ -103,6 +107,8 @@ import * as TaskApi from '@/api/bpm/task'
import TaskUpdateAssigneeForm from './TaskUpdateAssigneeForm.vue'
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
import TaskReturnDialog from './TaskReturnDialogForm.vue'
import TaskDelegateForm from './taskDelegateForm.vue'
import { registerComponent } from '@/utils/routerHelper'
defineOptions({ name: 'BpmProcessInstanceDetail' })
@ -166,16 +172,17 @@ const openTaskUpdateAssigneeForm = (id: string) => {
taskUpdateAssigneeFormRef.value.open(id)
}
const taskDelegateForm = ref()
/** 处理审批退回的操作 */
const handleDelegate = async (task) => {
message.error('暂不支持【委派】功能,可以使用【转派】替代!')
console.log(task)
taskDelegateForm.value.open(task.id)
}
//退
const taskReturnDialogRef = ref()
/** 处理审批退回的操作 */
const handleBack = async (task) => {
message.error('暂不支持【退回】功能!')
console.log(task)
taskReturnDialogRef.value.open(task.id)
}
/** 获得详情 */
@ -256,7 +263,7 @@ const getTaskList = async () => {
auditForms.value = []
tasks.value.forEach((task) => {
// 2.1
if (task.result !== 1) {
if (task.result !== 1 && task.result !== 6) {
return
}
// 2.2

View File

@ -2,7 +2,7 @@
<ContentWrap>
<el-row>
<el-col>
<div class="mb-2 float-right">
<div class="float-right mb-2">
<el-button size="small" type="primary" @click="showJson"> JSON</el-button>
<el-button size="small" type="success" @click="showOption"> Options</el-button>
<el-button size="small" type="danger" @click="showTemplate"></el-button>

View File

@ -20,8 +20,8 @@
ref="treeRef"
:data="preview.fileTree"
:expand-on-click-node="false"
default-expand-all
highlight-current
default-expand-all
node-key="id"
@node-click="handleNodeClick"
/>
@ -31,7 +31,7 @@
<el-card
v-loading="loading"
:gutter="12"
class="w-2/3 ml-3"
class="ml-3 w-2/3"
element-loading-text="加载代码中..."
shadow="hover"
>

View File

@ -7,7 +7,7 @@
</div>
</template>
<div class="flex items-center">
<span class="text-lg font-medium mr-4"> 连接状态: </span>
<span class="mr-4 text-lg font-medium"> 连接状态: </span>
<el-tag :color="getTagColor">{{ status }}</el-tag>
</div>
<hr class="my-4" />
@ -20,7 +20,7 @@
{{ getIsOpen ? '关闭连接' : '开启连接' }}
</el-button>
</div>
<p class="text-lg font-medium mt-4">设置</p>
<p class="mt-4 text-lg font-medium">设置</p>
<hr class="my-4" />
<el-input
v-model="sendValue"
@ -43,7 +43,7 @@
<ul>
<li v-for="item in getList" :key="item.time" class="mt-2">
<div class="flex items-center">
<span class="mr-2 text-primary font-medium">收到消息:</span>
<span class="text-primary mr-2 font-medium">收到消息:</span>
<span>{{ formatDate(item.time) }}</span>
</div>
<div>

View File

@ -35,14 +35,14 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" row-key="id" default-expand-all>
<el-table-column label="分类名称" prop="name" sortable />
<el-table-column label="移动端分类图" align="center" prop="picUrl">
<el-table-column label="名称" min-width="240" prop="name" sortable />
<el-table-column label="分类图" align="center" min-width="80" prop="picUrl">
<template #default="scope">
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="移动端分类图" class="h-30px" />
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="移动端分类图" class="h-36px" />
</template>
</el-table-column>
<el-table-column label="分类排序" align="center" prop="sort" />
<el-table-column label="开启状态" align="center" prop="status">
<el-table-column label="排序" align="center" min-width="150" prop="sort" />
<el-table-column label="状态" align="center" min-width="150" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>

View File

@ -8,7 +8,7 @@
v-loading="formLoading"
>
<el-form-item label="商品" prop="spuId">
<div @click="handleSelectSpu" class="w-60px h-60px">
<div @click="handleSelectSpu" class="h-60px w-60px">
<div v-if="spuData && spuData.picUrl">
<el-image :src="spuData.picUrl" />
</div>
@ -18,7 +18,7 @@
</div>
</el-form-item>
<el-form-item label="商品规格" prop="skuId" v-if="formData.spuId">
<div @click="handleSelectSku" class="w-60px h-60px">
<div @click="handleSelectSku" class="h-60px w-60px">
<div v-if="skuData && skuData.picUrl">
<el-image :src="skuData.picUrl" />
</div>
@ -150,6 +150,7 @@ const resetForm = () => {
userNickname: undefined,
userAvatar: undefined,
spuId: undefined,
spuName: undefined,
skuId: undefined,
descriptionScores: 5,
benefitScores: 5,
@ -182,11 +183,11 @@ const handleSkuChange = (sku: ProductSpuApi.Sku) => {
<style>
.select-box {
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed var(--el-border-color-darker);
border-radius: 8px;
width: 100%;
height: 100%;
border: 1px dashed var(--el-border-color-darker);
border-radius: 8px;
align-items: center;
justify-content: center;
}
</style>

View File

@ -59,16 +59,15 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="false">
<el-table-column label="评论编号" align="center" prop="id" min-width="60" />
<el-table-column label="用户名称" align="center" prop="userNickname" width="80" />
<el-table-column label="商品信息" align="center" min-width="300">
<el-table-column label="评论编号" align="center" prop="id" min-width="50" />
<el-table-column label="商品信息" align="center" min-width="400">
<template #default="scope">
<div class="flex row items-center gap-x-4px">
<div class="row flex items-center gap-x-4px">
<el-image
v-if="scope.row.skuPicUrl"
:src="scope.row.skuPicUrl"
:preview-src-list="[scope.row.skuPicUrl]"
class="w-40px h-40px shrink-0"
class="h-40px w-40px shrink-0"
preview-teleported
/>
<div>{{ scope.row.spuName }}</div>
@ -82,10 +81,10 @@
</div>
</template>
</el-table-column>
<el-table-column label="评分星级" align="center" prop="scores" width="80" />
<el-table-column label="描述星级" align="center" prop="descriptionScores" width="80" />
<el-table-column label="服务星级" align="center" prop="benefitScores" width="80" />
<el-table-column label="评论内容" align="center" prop="content" min-width="80">
<el-table-column label="用户名称" align="center" prop="userNickname" width="100" />
<el-table-column label="商品评分" align="center" prop="descriptionScores" width="90" />
<el-table-column label="服务评分" align="center" prop="benefitScores" width="90" />
<el-table-column label="评论内容" align="center" prop="content" min-width="210">
<template #default="scope">
<p>{{ scope.row.content }}</p>
<div class="flex justify-center gap-x-4px">
@ -95,7 +94,7 @@
:src="picUrl"
:preview-src-list="scope.row.picUrls"
:initial-index="index"
class="w-40px h-40px"
class="h-40px w-40px"
preview-teleported
/>
</div>
@ -105,7 +104,7 @@
label="回复内容"
align="center"
prop="replyContent"
min-width="100"
min-width="250"
show-overflow-tooltip
/>
<el-table-column
@ -113,7 +112,7 @@
align="center"
prop="createTime"
:formatter="dateFormatter"
width="170"
width="180"
/>
<el-table-column label="是否展示" align="center" width="80px">
<template #default="scope">

View File

@ -53,8 +53,8 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="编号" prop="id" />
<el-table-column align="center" label="名称" prop="name" />
<el-table-column align="center" label="编号" min-width="60" prop="id" />
<el-table-column align="center" label="属性名称" prop="name" min-width="150" />
<el-table-column :show-overflow-tooltip="true" align="center" label="备注" prop="remark" />
<el-table-column
:formatter="dateFormatter"
@ -165,7 +165,7 @@ const handleDelete = async (id: number) => {
/** 跳转商品属性列表 */
const goValueList = (id: number) => {
push({ path: '/product/property/value/' + id })
push({ name: 'ProductPropertyValue', params: { propertyId: id } })
}
/** 初始化 **/

View File

@ -45,8 +45,8 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名称" align="center" prop="name" :show-overflow-tooltip="true" />
<el-table-column label="编号" align="center" min-width="60" prop="id" />
<el-table-column label="属性值名称" align="center" min-width="150" prop="name" />
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column
label="创建时间"

View File

@ -124,7 +124,7 @@
<el-table-column v-if="isComponent" type="selection" width="45" />
<el-table-column align="center" label="图片" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
<el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" />
</template>
</el-table-column>
<template v-if="formData!.specType && !isBatch">
@ -204,7 +204,7 @@
<el-table-column v-if="isComponent" type="selection" width="45" />
<el-table-column align="center" label="图片" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
<el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" />
</template>
</el-table-column>
<template v-if="formData!.specType">

View File

@ -12,7 +12,7 @@
<template #default="{ row }">
<el-image
:src="row.picUrl"
class="w-30px h-30px"
class="h-30px w-30px"
:preview-src-list="[row.picUrl]"
preview-teleported
/>
@ -25,7 +25,7 @@
</el-table-column>
<el-table-column align="center" label="销售价(元)" min-width="80">
<template #default="{ row }">
{{ row.price }}
{{ fenToYuan(row.price) }}
</template>
</el-table-column>
</el-table>
@ -36,6 +36,7 @@
import { ElTable } from 'element-plus'
import * as ProductSpuApi from '@/api/mall/product/spu'
import { propTypes } from '@/utils/propTypes'
import { fenToYuan } from '@/utils'
defineOptions({ name: 'SkuTableSelect' })

View File

@ -73,7 +73,7 @@
<template #default="{ row }">
<el-image
:src="row.picUrl"
class="w-30px h-30px"
class="h-30px w-30px"
:preview-src-list="[row.picUrl]"
preview-teleported
/>

View File

@ -15,15 +15,14 @@
</el-col>
<el-col :span="12">
<el-form-item label="商品分类" prop="categoryId">
<el-tree-select
<el-cascader
v-model="formData.categoryId"
:data="categoryList"
:options="categoryList"
:props="defaultProps"
check-strictly
class="w-1/1"
node-key="id"
clearable
placeholder="请选择商品分类"
@change="categoryNodeClick"
filterable
/>
</el-form-item>
</el-col>
@ -74,8 +73,6 @@
:value="item.id"
/>
</el-select>
<!-- TODO 可能情况善品录入后选择运费发现下拉选择中没有对应的模版 这里需不需要做添加运费模版后选择的功能 -->
<!-- <el-button class="ml-20px">运费模板</el-button>-->
</el-form-item>
</el-col>
<el-col :span="12">
@ -102,7 +99,7 @@
<el-form-item label="分销类型" props="subCommissionType">
<el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
<el-radio :label="false">默认设置</el-radio>
<el-radio :label="true" class="radio">自行设置</el-radio>
<el-radio :label="true" class="radio">单独设置</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
@ -117,7 +114,7 @@
/>
</el-form-item>
<el-form-item v-if="formData.specType" label="商品属性">
<el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open"></el-button>
<el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open"></el-button>
<ProductAttributes :propertyList="propertyList" @success="generateSkus" />
</el-form-item>
<template v-if="formData.specType && propertyList.length > 0">
@ -139,7 +136,7 @@
<!-- 情况二详情 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #categoryId="{ row }"> {{ categoryString(row.categoryId) }}</template>
<template #categoryId="{ row }"> {{ formatCategoryName(row.categoryId) }}</template>
<template #brandId="{ row }">
{{ brandList.find((item) => item.id === row.brandId)?.name }}
</template>
@ -150,17 +147,17 @@
{{ row.specType ? '多规格' : '单规格' }}
</template>
<template #subCommissionType="{ row }">
{{ row.subCommissionType ? '自行设置' : '默认设置' }}
{{ row.subCommissionType ? '单独设置' : '默认设置' }}
</template>
<template #picUrl="{ row }">
<el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
<el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" />
</template>
<template #sliderPicUrls="{ row }">
<el-image
v-for="(item, index) in row.sliderPicUrls"
:key="index"
:src="item.url"
class="w-60px h-60px mr-10px"
class="mr-10px h-60px w-60px"
@click="imagePreview(row.sliderPicUrls)"
/>
</template>
@ -206,17 +203,17 @@ const ruleConfig: RuleConfig[] = [
{
name: 'price',
rule: (arg) => arg >= 0.01,
message: '商品销售价格必须大于等于 0.01 '
message: '商品销售价格必须大于等于 0.01 '
},
{
name: 'marketPrice',
rule: (arg) => arg >= 0.01,
message: '商品市场价格必须大于等于 0.01 '
message: '商品市场价格必须大于等于 0.01 '
},
{
name: 'costPrice',
rule: (arg) => arg >= 0.01,
message: '商品成本价格必须大于等于 0.01 '
message: '商品成本价格必须大于等于 0.01 '
}
]
@ -359,23 +356,11 @@ const onChangeSpec = () => {
}
const categoryList = ref([]) //
/**
* 选择分类时触发校验
*/
const categoryNodeClick = () => {
if (!checkSelectedNode(categoryList.value, formData.categoryId)) {
formData.categoryId = null
message.warning('必须选择二级及以下节点!!')
}
}
/**
* 获取分类的节点的完整结构
*
* @param categoryId 分类id
*/
const categoryString = (categoryId) => {
/** 获取分类的节点的完整结构 */
const formatCategoryName = (categoryId) => {
return treeToString(categoryList.value, categoryId)
}
const brandList = ref([]) //
const deliveryTemplateList = ref([]) //
onMounted(async () => {

View File

@ -41,7 +41,7 @@
</el-form-item>
</el-col>
<el-col :span="24">
<!-- TODO tag展示暂时不考虑排序 -->
<!-- TODO @puhui999tag展示暂时不考虑排序支持拖动排序 -->
<el-form-item label="活动优先级">
<el-tag>默认</el-tag>
<el-tag class="ml-2" type="success">秒杀</el-tag>

View File

@ -102,7 +102,7 @@ const getDetail = async () => {
if ('ProductSpuDetail' === name) {
isDetail.value = true
}
const id = params.spuId as unknown as number
const id = params.id as unknown as number
if (id) {
formLoading.value = true
try {
@ -161,7 +161,7 @@ const submitForm = async () => {
deepCopyFormData.sliderPicUrls = newSliderPicUrls
//
const data = deepCopyFormData as ProductSpuApi.Spu
const id = params.spuId as unknown as number
const id = params.id as unknown as number
if (!id) {
await ProductSpuApi.createSpu(data)
message.success(t('common.createSuccess'))

View File

@ -18,15 +18,14 @@
/>
</el-form-item>
<el-form-item label="商品分类" prop="categoryId">
<el-tree-select
<el-cascader
v-model="queryParams.categoryId"
:data="categoryList"
:options="categoryList"
:props="defaultProps"
check-strictly
class="w-1/1"
node-key="id"
clearable
placeholder="请选择商品分类"
@change="nodeClick"
filterable
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
@ -78,7 +77,7 @@
/>
</el-tabs>
<el-table v-loading="loading" :data="list">
<el-table-column type="expand" width="30">
<el-table-column type="expand">
<template #default="{ row }">
<el-form class="spu-table-expand" label-position="left">
<el-row>
@ -86,17 +85,17 @@
<el-row>
<el-col :span="8">
<el-form-item label="商品分类:">
<span>{{ categoryString(row.categoryId) }}</span>
<span>{{ formatCategoryName(row.categoryId) }}</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="市场价:">
<span>{{ floatToFixed2(row.marketPrice) }}</span>
<span>{{ fenToYuan(row.marketPrice) }}</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="成本价:">
<span>{{ floatToFixed2(row.costPrice) }}</span>
<span>{{ fenToYuan(row.costPrice) }}</span>
</el-form-item>
</el-col>
</el-row>
@ -106,9 +105,8 @@
<el-col :span="24">
<el-row>
<el-col :span="8">
<el-form-item label="收藏:">
<!-- TODO 没有这个属性暂时写死 5 -->
<span>5</span>
<el-form-item label="浏览量:">
<span>{{ row.browseCount }}</span>
</el-form-item>
</el-col>
<el-col :span="8">
@ -122,15 +120,15 @@
</el-form>
</template>
</el-table-column>
<el-table-column key="id" align="center" label="商品编号" prop="id" />
<el-table-column align="center" label="商品编号" min-width="60" prop="id" />
<el-table-column label="商品图" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
<el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" />
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
<el-table-column align="center" label="商品售价" min-width="90" prop="price">
<template #default="{ row }"> {{ floatToFixed2(row.price) }}</template>
<template #default="{ row }"> {{ fenToYuan(row.price) }}</template>
</el-table-column>
<el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
<el-table-column align="center" label="库存" min-width="90" prop="stock" />
@ -152,7 +150,7 @@
active-text="上架"
inactive-text="下架"
inline-prompt
@change="changeStatus(row)"
@change="handleStatusChange(row)"
/>
</template>
<template v-else>
@ -191,7 +189,7 @@
v-hasPermi="['product:spu:update']"
link
type="primary"
@click="changeStatus(row, ProductSpuStatusEnum.DISABLE.status)"
@click="handleStatus02Change(row, ProductSpuStatusEnum.DISABLE.status)"
>
恢复到仓库
</el-button>
@ -201,7 +199,7 @@
v-hasPermi="['product:spu:update']"
link
type="primary"
@click="changeStatus(row, ProductSpuStatusEnum.RECYCLE.status)"
@click="handleStatus02Change(row, ProductSpuStatusEnum.RECYCLE.status)"
>
加入回收站
</el-button>
@ -220,12 +218,11 @@
</template>
<script lang="ts" setup>
import { TabsPaneContext } from 'element-plus'
import { cloneDeep } from 'lodash-es'
import { createImageViewer } from '@/components/ImageViewer'
import { dateFormatter } from '@/utils/formatTime'
import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree'
import { defaultProps, handleTree, treeToString } from '@/utils/tree'
import { ProductSpuStatusEnum } from '@/utils/constants'
import { floatToFixed2 } from '@/utils'
import { fenToYuan } from '@/utils'
import download from '@/utils/download'
import * as ProductSpuApi from '@/api/mall/product/spu'
import * as ProductCategoryApi from '@/api/mall/product/category'
@ -254,7 +251,7 @@ const tabsData = ref([
},
{
count: 0,
name: '已经售空商品',
name: '已售罄商品',
type: 2
},
{
@ -303,43 +300,37 @@ const getList = async () => {
}
}
/**
* 更改 SPU 状态
*
* @param row
* @param status 更改前的值
*/
const changeStatus = async (row, status?: number) => {
const deepCopyValue = cloneDeep(unref(row))
if (typeof status !== 'undefined') deepCopyValue.status = status
/** 添加到仓库 / 回收站的状态 */
const handleStatus02Change = async (row, newStatus: number) => {
try {
let text = ''
switch (deepCopyValue.status) {
case ProductSpuStatusEnum.DISABLE.status:
text = ProductSpuStatusEnum.DISABLE.name
break
case ProductSpuStatusEnum.ENABLE.status:
text = ProductSpuStatusEnum.ENABLE.name
break
case ProductSpuStatusEnum.RECYCLE.status:
text = `加入${ProductSpuStatusEnum.RECYCLE.name}`
break
}
await message.confirm(
deepCopyValue.status === -1
? `确认要将[${row.name}]${text}吗?`
: row.status === -1 // -1: status-1 status0
? `确认要将[${row.name}]恢复到仓库吗?`
: `确认要${text}[${row.name}]吗?`
)
await ProductSpuApi.updateStatus({ id: deepCopyValue.id, status: deepCopyValue.status })
message.success('更新状态成功')
//
const text = newStatus === ProductSpuStatusEnum.RECYCLE.status ? '加入到回收站' : '恢复到仓库'
await message.confirm(`确认要"${row.name}"${text}吗?`)
//
await ProductSpuApi.updateStatus({ id: row.id, status: newStatus })
message.success(text + '成功')
// tabs
await getTabsCount()
//
await getList()
} catch {}
}
/** 更新上架/下架状态 */
const handleStatusChange = async (row) => {
try {
//
const text = row.status ? '上架' : '下架'
await message.confirm(`确认要${text}"${row.name}"吗?`)
//
await ProductSpuApi.updateStatus({ id: row.id, status: row.status })
message.success(text + '成功')
// tabs
await getTabsCount()
//
await getList()
} catch {
//
//
row.status =
row.status === ProductSpuStatusEnum.DISABLE.status
? ProductSpuStatusEnum.ENABLE.status
@ -380,26 +371,20 @@ const resetQuery = () => {
handleQuery()
}
/**
* 新增或修改
*
* @param id 商品 SPU 编号
*/
/** 新增或修改 */
const openForm = (id?: number) => {
//
if (typeof id === 'number') {
push({ name: 'ProductSpuEdit', params: { spuId: id } })
push({ name: 'ProductSpuEdit', params: { id } })
return
}
//
push({ name: 'ProductSpuAdd' })
}
/**
* 查看商品详情
*/
/** 查看商品详情 */
const openDetail = (id: number) => {
push({ name: 'ProductSpuDetail', params: { spuId: id } })
push({ name: 'ProductSpuDetail', params: { id } })
}
/** 导出按钮操作 */
@ -417,6 +402,12 @@ const handleExport = async () => {
}
}
const categoryList = ref() //
/** 获取分类的节点的完整结构 */
const formatCategoryName = (categoryId) => {
return treeToString(categoryList.value, categoryId)
}
//
watch(
() => currentRoute.value,
@ -425,25 +416,6 @@ watch(
}
)
const categoryList = ref() //
/**
* 获取分类的节点的完整结构
* @param categoryId 分类id
*/
const categoryString = (categoryId) => {
return treeToString(categoryList.value, categoryId)
}
/**
* 校验所选是否为二级及以下节点
*/
const nodeClick = () => {
if (!checkSelectedNode(categoryList.value, queryParams.value.categoryId)) {
queryParams.value.categoryId = null
message.warning('必须选择二级及以下节点!!')
}
}
/** 初始化 **/
onMounted(async () => {
await getTabsCount()

View File

@ -30,7 +30,7 @@
<el-table-column align="center" label="砍价底价(元)" min-width="168">
<template #default="{ row: sku }">
<el-input-number
v-model="sku.productConfig.bargainPrice"
v-model="sku.productConfig.bargainMinPrice"
:min="0"
:precision="2"
:step="0.1"
@ -86,7 +86,7 @@ const ruleConfig: RuleConfig[] = [
message: '商品砍价起始价格不能小于 0 '
},
{
name: 'productConfig.bargainPrice',
name: 'productConfig.bargainMinPrice',
rule: (arg) => arg >= 0,
message: '商品砍价底价不能小于 0 '
},
@ -123,14 +123,14 @@ const getSpuDetails = async (
spuId: spu.id!,
skuId: sku.id!,
bargainFirstPrice: 1,
bargainPrice: 1,
bargainMinPrice: 1,
stock: 1
}
if (typeof products !== 'undefined') {
const product = products.find((item) => item.skuId === sku.id)
if (product) {
product.bargainFirstPrice = formatToFraction(product.bargainFirstPrice)
product.bargainPrice = formatToFraction(product.bargainPrice)
product.bargainMinPrice = formatToFraction(product.bargainMinPrice)
}
config = product || config
}
@ -173,7 +173,7 @@ const open = async (type: string, id?: number) => {
spuId: data.spuId!,
skuId: data.skuId,
bargainFirstPrice: data.bargainFirstPrice, //
bargainPrice: data.bargainPrice, //
bargainMinPrice: data.bargainMinPrice, //
stock: data.stock //
}
]
@ -204,12 +204,13 @@ const submitForm = async () => {
//
formLoading.value = true
try {
// TODO @puhui999:
const data = formRef.value.formModel as BargainActivityApi.BargainActivityVO
const products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
products.forEach((item: BargainProductVO) => {
//
item.bargainFirstPrice = convertToInteger(item.bargainFirstPrice)
item.bargainPrice = convertToInteger(item.bargainPrice)
item.bargainMinPrice = convertToInteger(item.bargainMinPrice)
})
// ,
data.randomMinPrice = convertToInteger(data.randomMinPrice)

View File

@ -6,7 +6,7 @@ export const rules = reactive({
name: [required],
startTime: [required],
endTime: [required],
userSize: [required],
helpMaxCount: [required],
bargainCount: [required],
singleLimitCount: [required]
})
@ -72,7 +72,7 @@ const crudSchemas = reactive<CrudSchema[]>([
},
{
label: '砍价人数',
field: 'userSize',
field: 'helpMaxCount',
isSearch: false,
form: {
component: 'InputNumber',
@ -132,20 +132,6 @@ const crudSchemas = reactive<CrudSchema[]>([
value: 0
}
},
{
label: '砍价成功数量',
field: 'successCount',
isSearch: false,
isForm: false
},
{
label: '活动状态',
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isSearch: true,
isForm: false
},
{
label: '拼团商品',
field: 'spuId',
@ -155,11 +141,6 @@ const crudSchemas = reactive<CrudSchema[]>([
span: 24
}
}
},
{
label: '操作',
field: 'action',
isForm: false
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@ -1,90 +1,195 @@
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams">
<!-- 新增等操作按钮 -->
<template #actionMore>
<el-button
v-hasPermi="['promotion:bargain-activity:create']"
plain
type="primary"
@click="openForm('create')"
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="活动名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入活动名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="活动状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择活动状态"
clearable
class="!w-240px"
>
<Icon class="mr-5px" icon="ep:plus" />
新增
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['promotion:bargain-activity:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</template>
</Search>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<Table
v-model:currentPage="tableObject.currentPage"
v-model:pageSize="tableObject.pageSize"
:columns="allSchemas.tableColumns"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
>
<template #spuId="{ row }">
<el-image
:src="row.picUrl"
class="w-30px h-30px align-middle mr-5px"
@click="imagePreview(row.picUrl)"
/>
<span class="align-middle">{{ row.spuName }}</span>
</template>
<template #action="{ row }">
<el-button
v-hasPermi="['promotion:bargain-activity:update']"
link
type="primary"
@click="openForm('update', row.id)"
>
编辑
</el-button>
<el-button
v-hasPermi="['promotion:bargain-activity:delete']"
link
type="danger"
@click="handleDelete(row.id)"
>
删除
</el-button>
</template>
</Table>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="活动编号" prop="id" min-width="80" />
<el-table-column label="活动名称" prop="name" min-width="140" />
<el-table-column label="活动时间" min-width="210">
<template #default="scope">
{{ formatDate(scope.row.startTime, 'YYYY-MM-DD') }}
~ {{ formatDate(scope.row.endTime, 'YYYY-MM-DD') }}
</template>
</el-table-column>
<el-table-column label="商品图片" prop="spuName" min-width="80">
<template #default="scope">
<el-image
:src="scope.row.picUrl"
class="h-40px w-40px"
:preview-src-list="[scope.row.picUrl]"
preview-teleported
/>
</template>
</el-table-column>
<el-table-column label="商品标题" prop="spuName" min-width="300" />
<el-table-column
label="起始价格"
prop="bargainFirstPrice"
min-width="100"
:formatter="fenToYuanFormat"
/>
<el-table-column
label="砍价底价"
prop="bargainMinPrice"
min-width="100"
:formatter="fenToYuanFormat"
/>
<el-table-column label="总砍价人数" prop="recordUserCount" min-width="100" />
<el-table-column label="成功砍价人数" prop="recordSuccessUserCount" min-width="110" />
<el-table-column label="助力人数" prop="helpUserCount" min-width="100" />
<el-table-column label="活动状态" align="center" prop="status" min-width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="库存" align="center" prop="stock" min-width="80" />
<el-table-column label="总库存" align="center" prop="totalStock" min-width="80" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" width="150px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['promotion:bargain-activity:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleClose(scope.row.id)"
v-if="scope.row.status === 0"
v-hasPermi="['promotion:bargain-activity:close']"
>
关闭
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-else
v-hasPermi="['promotion:bargain-activity:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<BargainActivityForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
import { allSchemas } from './bargainActivity.data'
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as BargainActivityApi from '@/api/mall/promotion/bargain/bargainActivity'
import BargainActivityForm from './BargainActivityForm.vue'
import { createImageViewer } from '@/components/ImageViewer'
import { sortTableColumns } from '@/hooks/web/useCrudSchemas'
import { formatDate } from '@/utils/formatTime'
import { fenToYuanFormat } from '@/utils/formatter'
import { fenToYuan } from '@/utils'
defineOptions({ name: 'PromotionBargainActivity' })
// tableObject
// tableMethods
// https://doc.iocoder.cn/vue3/crud-schema/
const { tableObject, tableMethods } = useTable({
getListApi: BargainActivityApi.getBargainActivityPage, //
delListApi: BargainActivityApi.deleteBargainActivity //
})
//
const { getList, setSearchParams } = tableMethods
const message = useMessage() //
const { t } = useI18n() //
/** 商品图预览 */
const imagePreview = (imgUrl: string) => {
createImageViewer({
urlList: [imgUrl]
})
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
status: null
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await BargainActivityApi.getBargainActivityPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
@ -93,15 +198,35 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
// TODO
/** 关闭按钮操作 */
const handleClose = async (id: number) => {
try {
//
await message.confirm('确认关闭该秒杀活动吗?')
//
await BargainActivityApi.closeSeckillActivity(id)
message.success('关闭成功')
//
await getList()
} catch {}
}
/** 删除按钮操作 */
const handleDelete = (id: number) => {
tableMethods.delList(id, false)
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await BargainActivityApi.deleteBargainActivity(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
//
sortTableColumns(allSchemas.tableColumns, 'spuId')
getList()
onMounted(async () => {
await getList()
})
</script>

View File

@ -0,0 +1,90 @@
<template>
<Dialog v-model="dialogVisible" title="助力列表">
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="用户编号" prop="userId" min-width="80px" />
<el-table-column label="用户头像" prop="avatar" min-width="80px">
<template #default="scope">
<el-avatar :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column label="用户昵称" prop="nickname" min-width="100px" />
<el-table-column
label="砍价金额"
prop="reducePrice"
min-width="100px"
:formatter="fenToYuanFormat"
/>
<el-table-column
label="助力时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</Dialog>
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import * as BargainHelpApi from '@/api/mall/promotion/bargain/bargainHelp'
import { fenToYuanFormat } from '@/utils/formatter'
/** 助力列表 */
defineOptions({ name: 'BargainRecordListDialog' })
const message = useMessage() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
recordId: undefined
})
const queryFormRef = ref() //
/** 打开弹窗 */
const dialogVisible = ref(false) //
const open = async (recordId: any) => {
dialogVisible.value = true
queryParams.recordId = recordId
resetQuery()
}
defineExpose({ open }) // open
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await BargainHelpApi.getBargainHelpPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
</script>

View File

@ -0,0 +1,195 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="砍价状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择砍价状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_BARGAIN_RECORD_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['promotion:bargain-record:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['promotion:bargain-record:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编号" min-width="50" prop="id" />
<el-table-column label="发起用户" min-width="120">
<template #default="scope">
<el-image
:src="scope.row.avatar"
class="h-20px w-20px"
:preview-src-list="[scope.row.avatar]"
preview-teleported
/>
{{ scope.row.nickname }}
</template>
</el-table-column>
<el-table-column
label="发起时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="砍价活动" min-width="150" prop="activity.name" />
<el-table-column
label="最低价"
min-width="100"
prop="activity.bargainMinPrice"
:formatter="fenToYuanFormat"
/>
<el-table-column
label="当前价"
min-width="100"
prop="bargainPrice"
:formatter="fenToYuanFormat"
/>
<el-table-column label="总砍价次数" min-width="100" prop="activity.helpMaxCount" />
<el-table-column label="剩余砍价次数" min-width="100" prop="helpCount" />
<el-table-column label="砍价状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_BARGAIN_RECORD_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="结束时间"
align="center"
prop="endTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="订单编号" align="center" prop="orderId" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openRecordListDialog(scope.row.id)"
v-hasPermi="['promotion:bargain-help:query']"
>
助力
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗 -->
<BargainRecordListDialog ref="recordListDialogRef" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as BargainRecordApi from '@/api/mall/promotion/bargain/bargainRecord'
import { fenToYuanFormat } from '@/utils/formatter'
import BargainRecordListDialog from './BargainRecordListDialog.vue'
defineOptions({ name: 'PromotionBargainRecord' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
status: null,
createTime: []
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await BargainRecordApi.getBargainRecordPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 打开[助力]弹窗 */
const recordListDialogRef = ref()
const openRecordListDialog = (id?: number) => {
recordListDialogRef.value.open(id)
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -167,6 +167,7 @@ const submitForm = async () => {
products.forEach((item: CombinationActivityApi.CombinationProductVO) => {
item.combinationPrice = convertToInteger(item.combinationPrice)
})
// TODO @puhui999:
const data = formRef.value.formModel as CombinationActivityApi.CombinationActivityVO
data.products = products
//

View File

@ -33,7 +33,7 @@
<template #spuId="{ row }">
<el-image
:src="row.picUrl"
class="w-30px h-30px align-middle mr-5px"
class="mr-5px h-30px w-30px align-middle"
@click="imagePreview(row.picUrl)"
/>
<span class="align-middle">{{ row.spuName }}</span>

View File

@ -18,7 +18,7 @@
<el-table-column key="id" align="center" label="商品编号" prop="id" />
<el-table-column label="商品图" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
<el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" />
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />

View File

@ -70,7 +70,7 @@
<el-table-column key="id" align="center" label="商品编号" prop="id" />
<el-table-column label="商品图" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
<el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" />
</template>
</el-table-column>
<el-table-column

View File

@ -9,7 +9,7 @@ export const discountFormat = (row: CouponTemplateVO) => {
return `${floatToFixed2(row.discountPrice)}`
}
if (row.discountType === PromotionDiscountTypeEnum.PERCENT.type) {
return `${row.discountPrice}%`
return `${row.discountPercent}%`
}
return '未知【' + row.discountType + '】'
}

View File

@ -19,7 +19,7 @@
@keyup="handleQuery"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-form-item label="领取时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
@ -50,12 +50,17 @@
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="会员信息" align="center" prop="nickname" />
<!-- TODO 芋艿以后支持头像支持跳转 -->
<el-table-column label="优惠劵" align="center" prop="name" />
<el-table-column label="优惠券类型" align="center" prop="discountType">
<el-table-column label="会员昵称" align="center" min-width="100" prop="nickname" />
<el-table-column label="优惠券名称" align="center" min-width="140" prop="name" />
<el-table-column label="类型" align="center" prop="discountType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_PRODUCT_SCOPE" :value="scope.row.productScope" />
</template>
</el-table-column>
<el-table-column label="优惠" min-width="100" prop="discount">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
{{ discountFormat(scope.row) }}
</template>
</el-table-column>
<el-table-column label="领取方式" align="center" prop="takeType">
@ -109,6 +114,7 @@
import { deleteCoupon, getCouponPage } from '@/api/mall/promotion/coupon/coupon'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { discountFormat } from '@/views/mall/promotion/coupon/formatter'
defineOptions({ name: 'PromotionCoupon' })

View File

@ -26,7 +26,7 @@
v-if="formData.productScope === PromotionProductScopeEnum.SPU.scope"
prop="productSpuIds"
>
<div class="flex items-center gap-1 flex-wrap">
<div class="flex flex-wrap items-center gap-1">
<div class="select-box spu-pic" v-for="(spu, index) in productSpus" :key="spu.id">
<el-image :src="spu.picUrl" />
<Icon icon="ep:circle-close-filled" class="del-icon" @click="handleRemoveSpu(index)" />
@ -62,7 +62,7 @@
<el-input-number
v-model="formData.discountPrice"
placeholder="请输入优惠金额,单位:元"
class="!w-400px mr-2"
class="mr-2 !w-400px"
:precision="2"
:min="0"
/>
@ -76,7 +76,7 @@
<el-input-number
v-model="formData.discountPercent"
placeholder="优惠券折扣不能小于 1 折,且不可大于 9.9 折"
class="!w-400px mr-2"
class="mr-2 !w-400px"
:precision="1"
:min="1"
:max="9.9"
@ -91,7 +91,7 @@
<el-input-number
v-model="formData.discountLimitPrice"
placeholder="请输入最多优惠"
class="!w-400px mr-2"
class="mr-2 !w-400px"
:precision="2"
:min="0"
/>
@ -101,7 +101,7 @@
<el-input-number
v-model="formData.usePrice"
placeholder="无门槛请设为 0"
class="!w-400px mr-2"
class="mr-2 !w-400px"
:precision="2"
:min="0"
/>
@ -117,7 +117,7 @@
<el-input-number
v-model="formData.totalCount"
placeholder="发放数量,没有之后不能领取或发放,-1 为不限制"
class="!w-400px mr-2"
class="mr-2 !w-400px"
:precision="0"
:min="-1"
/>
@ -127,7 +127,7 @@
<el-input-number
v-model="formData.takeLimitCount"
placeholder="设置为 -1 时,可无限领取"
class="!w-400px mr-2"
class="mr-2 !w-400px"
:precision="0"
:min="-1"
/>
@ -423,22 +423,24 @@ const handleRemoveSpu = (index: number) => {
<style scoped lang="scss">
.select-box {
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed var(--el-border-color-darker);
border-radius: 8px;
width: 60px;
height: 60px;
border: 1px dashed var(--el-border-color-darker);
border-radius: 8px;
align-items: center;
justify-content: center;
}
.spu-pic {
position: relative;
}
.del-icon {
position: absolute;
top: -10px;
right: -10px;
z-index: 1;
width: 20px !important;
height: 20px !important;
right: -10px;
top: -10px;
}
</style>

View File

@ -19,7 +19,7 @@
@keyup="handleQuery"
/>
</el-form-item>
<el-form-item label="优惠类型" prop="discountType">
<el-form-item label="优惠类型" prop="discountType">
<el-select
v-model="queryParams.discountType"
class="!w-240px"
@ -71,14 +71,6 @@
>
<Icon class="mr-5px" icon="ep:plus" /> 新增
</el-button>
<el-button
plain
type="success"
@click="$router.push('/promotion/coupon')"
v-hasPermi="['promotion:coupon:query']"
>
<Icon icon="ep:operation" class="mr-5px" />会员优惠劵
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
@ -86,17 +78,29 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="优惠券名称" align="center" prop="name" />
<el-table-column label="优惠券类型" align="center" prop="discountType">
<el-table-column label="优惠券名称" min-width="140" prop="name" />
<el-table-column label="类型" min-width="80" prop="productScope">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_PRODUCT_SCOPE" :value="scope.row.productScope" />
</template>
</el-table-column>
<el-table-column label="优惠" min-width="100" prop="discount">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
{{ discountFormat(scope.row) }}
</template>
</el-table-column>
<el-table-column label="领取方式" min-width="100" prop="takeType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE" :value="scope.row.takeType" />
</template>
</el-table-column>
<el-table-column
label="优惠金额 / 折扣"
label="使用时间"
align="center"
prop="discount"
:formatter="discountFormat"
prop="validityType"
width="185"
:formatter="validityTypeFormat"
/>
<el-table-column label="发放数量" align="center" prop="totalCount" />
<el-table-column
@ -111,13 +115,6 @@
prop="takeLimitCount"
:formatter="takeLimitCountFormat"
/>
<el-table-column
label="有效期限"
align="center"
prop="validityType"
width="190"
:formatter="validityTypeFormat"
/>
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-switch

View File

@ -1,94 +1,205 @@
<template>
<doc-alert title="功能开启" url="https://doc.iocoder.cn/mall/build/" />
<!-- 搜索工作栏 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams">
<!-- 新增等操作按钮 -->
<template #actionMore>
<el-button
v-hasPermi="['promotion:seckill-activity:create']"
plain
type="primary"
@click="openForm('create')"
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="活动名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入活动名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="活动状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择活动状态"
clearable
class="!w-240px"
>
<Icon class="mr-5px" icon="ep:plus" /> 新增
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['promotion:seckill-activity:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</template>
</Search>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<Table
v-model:currentPage="tableObject.currentPage"
v-model:pageSize="tableObject.pageSize"
:columns="allSchemas.tableColumns"
:data="tableObject.tableList"
:expand="true"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
@expand-change="expandChange"
>
<template #expand> 展示活动商品和商品相关属性活动配置</template>
<template #spuId="{ row }">
<el-image
:src="row.picUrl"
class="w-30px h-30px align-middle mr-5px"
@click="imagePreview(row.picUrl)"
/>
<span class="align-middle">{{ row.spuName }}</span>
</template>
<template #configIds="{ row }">
<el-tag v-for="(name, index) in convertSeckillConfigNames(row)" :key="index" class="mr-5px">
{{ name }}
</el-tag>
</template>
<template #action="{ row }">
<el-button
v-hasPermi="['promotion:seckill-activity:update']"
link
type="primary"
@click="openForm('update', row.id)"
>
编辑
</el-button>
<el-button
v-hasPermi="['promotion:seckill-activity:delete']"
link
type="danger"
@click="handleDelete(row.id)"
>
删除
</el-button>
</template>
</Table>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="活动编号" prop="id" min-width="80" />
<el-table-column label="活动名称" prop="name" min-width="140" />
<el-table-column
label="秒杀时段"
prop="configIds"
width="220px"
:show-overflow-tooltip="false"
>
<template #default="scope">
<el-tag v-for="(configId, index) in scope.row.configIds" :key="index" class="mr-5px">
{{ formatConfigNames(configId) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="活动时间" min-width="210">
<template #default="scope">
{{ formatDate(scope.row.startTime, 'YYYY-MM-DD') }}
~ {{ formatDate(scope.row.endTime, 'YYYY-MM-DD') }}
</template>
</el-table-column>
<el-table-column label="商品图片" prop="spuName" min-width="80">
<template #default="scope">
<el-image
:src="scope.row.picUrl"
class="h-40px w-40px"
:preview-src-list="[scope.row.picUrl]"
preview-teleported
/>
</template>
</el-table-column>
<el-table-column label="商品标题" prop="spuName" min-width="300" />
<el-table-column
label="原价"
prop="marketPrice"
min-width="100"
:formatter="fenToYuanFormat"
/>
<el-table-column label="秒杀价" prop="marketPrice" min-width="100">
<template #default="scope">
{{ formatSeckillPrice(scope.row.products) }}
</template>
</el-table-column>
<el-table-column label="活动状态" align="center" prop="status" min-width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="库存" align="center" prop="stock" min-width="80" />
<el-table-column label="总库存" align="center" prop="totalStock" min-width="80" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" width="150px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['promotion:seckill-activity:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleClose(scope.row.id)"
v-if="scope.row.status === 0"
v-hasPermi="['promotion:seckill-activity:close']"
>
关闭
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-else
v-hasPermi="['promotion:seckill-activity:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<SeckillActivityForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
import { allSchemas } from './seckillActivity.data'
import { getSimpleSeckillConfigList } from '@/api/mall/promotion/seckill/seckillConfig'
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
import * as SeckillConfigApi from '@/api/mall/promotion/seckill/seckillConfig'
import SeckillActivityForm from './SeckillActivityForm.vue'
import { createImageViewer } from '@/components/ImageViewer'
import { sortTableColumns } from '@/hooks/web/useCrudSchemas'
import { formatDate } from '@/utils/formatTime'
import { fenToYuanFormat } from '@/utils/formatter'
import { fenToYuan } from '@/utils'
defineOptions({ name: 'PromotionSeckillActivity' })
defineOptions({ name: 'SeckillActivity' })
// tableObject
// tableMethods
// https://doc.iocoder.cn/vue3/crud-schema/
const { tableObject, tableMethods } = useTable({
getListApi: SeckillActivityApi.getSeckillActivityPage, //
delListApi: SeckillActivityApi.deleteSeckillActivity //
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
status: null
})
//
const { getList, setSearchParams } = tableMethods
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await SeckillActivityApi.getSeckillActivityPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
@ -96,37 +207,47 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = (id: number) => {
tableMethods.delList(id, false)
/** 关闭按钮操作 */
const handleClose = async (id: number) => {
try {
//
await message.confirm('确认关闭该秒杀活动吗?')
//
await SeckillActivityApi.closeSeckillActivity(id)
message.success('关闭成功')
//
await getList()
} catch {}
}
/** 商品图预览 */
const imagePreview = (imgUrl: string) => {
createImageViewer({
urlList: [imgUrl]
})
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await SeckillActivityApi.deleteSeckillActivity(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
const configList = ref([]) //
const convertSeckillConfigNames = computed(
() => (row) =>
configList.value
?.filter((item) => row.configIds.includes(item.id))
?.map((config) => config.name)
)
const formatConfigNames = (configId) => {
const config = configList.value.find((item) => item.id === configId)
return config != null ? `${config.name}[${config.startTime} ~ ${config.endTime}]` : ''
}
const expandChange = (row, expandedRows) => {
// TODO puhui CRUD
console.log(row, expandedRows)
const formatSeckillPrice = (products) => {
const seckillPrice = Math.min(...products.map((item) => item.seckillPrice))
return `${fenToYuan(seckillPrice)}`
}
/** 初始化 **/
onMounted(async () => {
//
sortTableColumns(allSchemas.tableColumns, 'spuId')
await getList()
//
configList.value = await getSimpleSeckillConfigList()
configList.value = await SeckillConfigApi.getSimpleSeckillConfigList()
})
</script>

Some files were not shown because too many files have changed in this diff Show More