Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
6e9e1d0888
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -102,6 +102,7 @@
|
|||||||
"codemirror",
|
"codemirror",
|
||||||
"commitlint",
|
"commitlint",
|
||||||
"cropperjs",
|
"cropperjs",
|
||||||
|
"echart",
|
||||||
"echarts",
|
"echarts",
|
||||||
"esnext",
|
"esnext",
|
||||||
"esno",
|
"esno",
|
||||||
@ -116,10 +117,12 @@
|
|||||||
"sider",
|
"sider",
|
||||||
"sortablejs",
|
"sortablejs",
|
||||||
"stylelint",
|
"stylelint",
|
||||||
|
"svgs",
|
||||||
"unocss",
|
"unocss",
|
||||||
"unplugin",
|
"unplugin",
|
||||||
"unref",
|
"unref",
|
||||||
"videojs",
|
"videojs",
|
||||||
|
"VITE",
|
||||||
"vitejs",
|
"vitejs",
|
||||||
"vueuse",
|
"vueuse",
|
||||||
"wangeditor",
|
"wangeditor",
|
||||||
|
33
package.json
33
package.json
@ -36,20 +36,20 @@
|
|||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
"@zxcvbn-ts/core": "^3.0.4",
|
"@zxcvbn-ts/core": "^3.0.4",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.6.0",
|
||||||
"benz-amr-recorder": "^1.1.5",
|
"benz-amr-recorder": "^1.1.5",
|
||||||
"bpmn-js-token-simulation": "^0.10.0",
|
"bpmn-js-token-simulation": "^0.10.0",
|
||||||
"camunda-bpmn-moddle": "^7.0.1",
|
"camunda-bpmn-moddle": "^7.0.1",
|
||||||
"cropperjs": "^1.6.1",
|
"cropperjs": "^1.6.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"diagram-js": "^12.5.0",
|
"diagram-js": "^12.6.0",
|
||||||
|
"driver.js": "^1.3.0",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
"echarts-wordcloud": "^2.1.0",
|
"echarts-wordcloud": "^2.1.0",
|
||||||
"element-plus": "2.4.1",
|
"element-plus": "2.4.1",
|
||||||
"fast-xml-parser": "^4.3.2",
|
"fast-xml-parser": "^4.3.2",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
"intro.js": "^7.2.0",
|
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"min-dash": "^4.1.1",
|
"min-dash": "^4.1.1",
|
||||||
@ -64,7 +64,7 @@
|
|||||||
"video.js": "^7.21.5",
|
"video.js": "^7.21.5",
|
||||||
"vue": "^3.3.7",
|
"vue": "^3.3.7",
|
||||||
"vue-dompurify-html": "^4.1.4",
|
"vue-dompurify-html": "^4.1.4",
|
||||||
"vue-i18n": "^9.5.0",
|
"vue-i18n": "^9.6.2",
|
||||||
"vue-router": "^4.2.5",
|
"vue-router": "^4.2.5",
|
||||||
"vue-types": "^5.1.1",
|
"vue-types": "^5.1.1",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
@ -72,20 +72,19 @@
|
|||||||
"xml-js": "^1.6.11"
|
"xml-js": "^1.6.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^18.0.0",
|
"@commitlint/cli": "^18.2.0",
|
||||||
"@commitlint/config-conventional": "^18.0.0",
|
"@commitlint/config-conventional": "^18.1.0",
|
||||||
"@iconify/json": "^2.2.132",
|
"@iconify/json": "^2.2.135",
|
||||||
"@intlify/unplugin-vue-i18n": "^1.4.0",
|
"@intlify/unplugin-vue-i18n": "^1.4.0",
|
||||||
"@purge-icons/generated": "^0.9.0",
|
"@purge-icons/generated": "^0.9.0",
|
||||||
"@types/intro.js": "^5.1.3",
|
|
||||||
"@types/lodash-es": "^4.17.10",
|
"@types/lodash-es": "^4.17.10",
|
||||||
"@types/node": "^20.8.8",
|
"@types/node": "^20.8.9",
|
||||||
"@types/nprogress": "^0.2.2",
|
"@types/nprogress": "^0.2.2",
|
||||||
"@types/qrcode": "^1.5.4",
|
"@types/qrcode": "^1.5.4",
|
||||||
"@types/qs": "^6.9.9",
|
"@types/qs": "^6.9.9",
|
||||||
"@types/sortablejs": "^1.15.4",
|
"@types/sortablejs": "^1.15.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.9.0",
|
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
||||||
"@typescript-eslint/parser": "^6.9.0",
|
"@typescript-eslint/parser": "^6.9.1",
|
||||||
"@unocss/transformer-variant-group": "^0.57.1",
|
"@unocss/transformer-variant-group": "^0.57.1",
|
||||||
"@unocss/eslint-config": "^0.57.1",
|
"@unocss/eslint-config": "^0.57.1",
|
||||||
"@vitejs/plugin-legacy": "^4.1.1",
|
"@vitejs/plugin-legacy": "^4.1.1",
|
||||||
@ -99,24 +98,24 @@
|
|||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-define-config": "^1.24.1",
|
"eslint-define-config": "^1.24.1",
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"eslint-plugin-vue": "^9.18.0",
|
"eslint-plugin-vue": "^9.18.1",
|
||||||
"lint-staged": "^15.0.2",
|
"lint-staged": "^15.0.2",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"postcss-html": "^1.5.0",
|
"postcss-html": "^1.5.0",
|
||||||
"postcss-scss": "^4.0.9",
|
"postcss-scss": "^4.0.9",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"rollup": "^4.1.4",
|
"rollup": "^4.1.5",
|
||||||
"sass": "^1.69.4",
|
"sass": "^1.69.5",
|
||||||
"stylelint": "^15.11.0",
|
"stylelint": "^15.11.0",
|
||||||
"stylelint-config-html": "^1.1.0",
|
"stylelint-config-html": "^1.1.0",
|
||||||
"stylelint-config-recommended": "^13.0.0",
|
"stylelint-config-recommended": "^13.0.0",
|
||||||
"stylelint-config-standard": "^34.0.0",
|
"stylelint-config-standard": "^34.0.0",
|
||||||
"stylelint-order": "^6.0.3",
|
"stylelint-order": "^6.0.3",
|
||||||
"terser": "^5.22.0",
|
"terser": "^5.23.0",
|
||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"unocss": "^0.57.1",
|
"unocss": "^0.57.1",
|
||||||
"unplugin-auto-import": "^0.16.6",
|
"unplugin-auto-import": "^0.16.7",
|
||||||
"unplugin-element-plus": "^0.8.0",
|
"unplugin-element-plus": "^0.8.0",
|
||||||
"unplugin-vue-components": "^0.25.2",
|
"unplugin-vue-components": "^0.25.2",
|
||||||
"vite": "4.5.0",
|
"vite": "4.5.0",
|
||||||
@ -128,7 +127,7 @@
|
|||||||
"vite-plugin-svg-icons": "^2.0.1",
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
"vite-plugin-top-level-await": "^1.3.1",
|
"vite-plugin-top-level-await": "^1.3.1",
|
||||||
"vue-eslint-parser": "^9.3.2",
|
"vue-eslint-parser": "^9.3.2",
|
||||||
"vue-tsc": "^1.8.20"
|
"vue-tsc": "^1.8.22"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
43
src/api/crm/product/index.ts
Normal file
43
src/api/crm/product/index.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
export interface ProductVO {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
no: string
|
||||||
|
unit: string
|
||||||
|
price: number
|
||||||
|
status: number
|
||||||
|
categoryId: number
|
||||||
|
description: string
|
||||||
|
ownerUserId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询产品列表
|
||||||
|
export const getProductPage = async (params) => {
|
||||||
|
return await request.get({ url: `/crm/product/page`, params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询产品详情
|
||||||
|
export const getProduct = async (id: number) => {
|
||||||
|
return await request.get({ url: `/crm/product/get?id=` + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增产品
|
||||||
|
export const createProduct = async (data: ProductVO) => {
|
||||||
|
return await request.post({ url: `/crm/product/create`, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改产品
|
||||||
|
export const updateProduct = async (data: ProductVO) => {
|
||||||
|
return await request.put({ url: `/crm/product/update`, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除产品
|
||||||
|
export const deleteProduct = async (id: number) => {
|
||||||
|
return await request.delete({ url: `/crm/product/delete?id=` + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出产品 Excel
|
||||||
|
export const exportProduct = async (params) => {
|
||||||
|
return await request.download({ url: `/crm/product/export-excel`, params })
|
||||||
|
}
|
33
src/api/crm/productCategory/index.ts
Normal file
33
src/api/crm/productCategory/index.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
// TODO @zange:挪到 product 下,建个 category 包,挪进去哈;
|
||||||
|
export interface ProductCategoryVO {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
parentId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询产品分类详情
|
||||||
|
export const getProductCategory = async (id: number) => {
|
||||||
|
return await request.get({ url: `/crm/product-category/get?id=` + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增产品分类
|
||||||
|
export const createProductCategory = async (data: ProductCategoryVO) => {
|
||||||
|
return await request.post({ url: `/crm/product-category/create`, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改产品分类
|
||||||
|
export const updateProductCategory = async (data: ProductCategoryVO) => {
|
||||||
|
return await request.put({ url: `/crm/product-category/update`, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除产品分类
|
||||||
|
export const deleteProductCategory = async (id: number) => {
|
||||||
|
return await request.delete({ url: `/crm/product-category/delete?id=` + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 产品分类列表
|
||||||
|
export const getProductCategoryList = async (params) => {
|
||||||
|
return await request.get({ url: `/crm/product-category/list`, params })
|
||||||
|
}
|
@ -2,7 +2,7 @@ import request from '@/config/axios'
|
|||||||
|
|
||||||
export interface ReceivablePlanVO {
|
export interface ReceivablePlanVO {
|
||||||
id: number
|
id: number
|
||||||
indexNo: number
|
period: number
|
||||||
receivableId: number
|
receivableId: number
|
||||||
status: number
|
status: number
|
||||||
checkStatus: string
|
checkStatus: string
|
||||||
|
37
src/api/system/social/client/index.ts
Normal file
37
src/api/system/social/client/index.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
export interface SocialClientVO {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
socialType: number
|
||||||
|
userType: number
|
||||||
|
clientId: string
|
||||||
|
clientSecret: string
|
||||||
|
agentId: string
|
||||||
|
status: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询社交客户端列表
|
||||||
|
export const getSocialClientPage = async (params) => {
|
||||||
|
return await request.get({ url: `/system/social-client/page`, params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询社交客户端详情
|
||||||
|
export const getSocialClient = async (id: number) => {
|
||||||
|
return await request.get({ url: `/system/social-client/get?id=` + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增社交客户端
|
||||||
|
export const createSocialClient = async (data: SocialClientVO) => {
|
||||||
|
return await request.post({ url: `/system/social-client/create`, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改社交客户端
|
||||||
|
export const updateSocialClient = async (data: SocialClientVO) => {
|
||||||
|
return await request.put({ url: `/system/social-client/update`, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除社交客户端
|
||||||
|
export const deleteSocialClient = async (id: number) => {
|
||||||
|
return await request.delete({ url: `/system/social-client/delete?id=` + id })
|
||||||
|
}
|
24
src/api/system/social/user/index.ts
Normal file
24
src/api/system/social/user/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
export interface SocialUserVO {
|
||||||
|
id: number
|
||||||
|
type: number
|
||||||
|
openid: string
|
||||||
|
token: string
|
||||||
|
rawTokenInfo: string
|
||||||
|
nickname: string
|
||||||
|
avatar: string
|
||||||
|
rawUserInfo: string
|
||||||
|
code: string
|
||||||
|
state: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询社交用户列表
|
||||||
|
export const getSocialUserPage = async (params) => {
|
||||||
|
return await request.get({ url: `/system/social-user/page`, params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询社交用户详情
|
||||||
|
export const getSocialUser = async (id: number) => {
|
||||||
|
return await request.get({ url: `/system/social-user/get?id=` + id })
|
||||||
|
}
|
@ -3,7 +3,7 @@ defineComponent({
|
|||||||
name: 'CardTitle'
|
name: 'CardTitle'
|
||||||
})
|
})
|
||||||
|
|
||||||
const { title } = defineProps({
|
defineProps({
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
<script lang="ts" setup>
|
<script setup lang="ts">
|
||||||
|
import { provide, computed, watch, onMounted } from 'vue'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { ComponentSize, ElConfigProvider } from 'element-plus'
|
||||||
import { useLocaleStore } from '@/store/modules/locale'
|
import { useLocaleStore } from '@/store/modules/locale'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { setCssVar } from '@/utils'
|
import { setCssVar } from '@/utils'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { ElementPlusSize } from '@/types/elementPlus'
|
|
||||||
import { useWindowSize } from '@vueuse/core'
|
|
||||||
|
|
||||||
defineOptions({ name: 'ConfigGlobal' })
|
|
||||||
|
|
||||||
const { variables } = useDesign()
|
const { variables } = useDesign()
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
size: propTypes.oneOf<ElementPlusSize>(['default', 'small', 'large']).def('default')
|
size: propTypes.oneOf<ComponentSize>(['default', 'small', 'large']).def('default')
|
||||||
})
|
})
|
||||||
|
|
||||||
provide('configGlobal', props)
|
provide('configGlobal', props)
|
||||||
@ -53,9 +52,9 @@ const currentLocale = computed(() => localeStore.currentLocale)
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ElConfigProvider
|
<ElConfigProvider
|
||||||
|
:namespace="variables.elNamespace"
|
||||||
:locale="currentLocale.elLocale"
|
:locale="currentLocale.elLocale"
|
||||||
:message="{ max: 1 }"
|
:message="{ max: 1 }"
|
||||||
:namespace="variables.elNamespace"
|
|
||||||
:size="size"
|
:size="size"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
@ -27,7 +27,7 @@ import * as DateUtil from '@/utils/formatTime'
|
|||||||
defineOptions({ name: 'ShortcutDateRangePicker' })
|
defineOptions({ name: 'ShortcutDateRangePicker' })
|
||||||
|
|
||||||
const shortcutDays = ref(7) // 日期快捷天数(单选按钮组), 默认7天
|
const shortcutDays = ref(7) // 日期快捷天数(单选按钮组), 默认7天
|
||||||
const times = ref<[dayjs.ConfigType, dayjs.ConfigType]>(['', '']) // 时间范围参数
|
const times = ref<[string, string]>(['', '']) // 时间范围参数
|
||||||
defineExpose({ times }) // 暴露时间范围参数
|
defineExpose({ times }) // 暴露时间范围参数
|
||||||
/** 日期快捷选择 */
|
/** 日期快捷选择 */
|
||||||
const shortcuts = [
|
const shortcuts = [
|
||||||
|
@ -32,7 +32,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
scrollContainer.value = getScrollContainer(refSticky.value!, true)
|
scrollContainer.value = getScrollContainer(refSticky.value!, true)
|
||||||
useEventListener(scrollContainer, 'scroll', handleScroll)
|
useEventListener(scrollContainer, 'scroll', handleScroll)
|
||||||
useEventListener('resize', handleReize)
|
useEventListener('resize', handleResize)
|
||||||
handleScroll()
|
handleScroll()
|
||||||
})
|
})
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
@ -103,7 +103,7 @@ const handleScroll = () => {
|
|||||||
reset()
|
reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const handleReize = () => {
|
const handleResize = () => {
|
||||||
if (isSticky.value && refSticky.value) {
|
if (isSticky.value && refSticky.value) {
|
||||||
width.value = refSticky.value.getBoundingClientRect().width + 'px'
|
width.value = refSticky.value.getBoundingClientRect().width + 'px'
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,13 @@ import { propTypes } from '@/utils/propTypes'
|
|||||||
defineOptions({ name: 'Tooltip' })
|
defineOptions({ name: 'Tooltip' })
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
titel: propTypes.string.def(''),
|
title: propTypes.string.def(''),
|
||||||
message: propTypes.string.def(''),
|
message: propTypes.string.def(''),
|
||||||
icon: propTypes.string.def('ep:question-filled')
|
icon: propTypes.string.def('ep:question-filled')
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<span>{{ titel }}</span>
|
<span>{{ title }}</span>
|
||||||
<ElTooltip :content="message" placement="top">
|
<ElTooltip :content="message" placement="top">
|
||||||
<Icon :icon="icon" class="relative top-1px ml-1px" />
|
<Icon :icon="icon" class="relative top-1px ml-1px" />
|
||||||
</ElTooltip>
|
</ElTooltip>
|
||||||
|
49
src/hooks/web/useGuide.ts
Normal file
49
src/hooks/web/useGuide.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { Config, driver } from 'driver.js'
|
||||||
|
import 'driver.js/dist/driver.css'
|
||||||
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const { variables } = useDesign()
|
||||||
|
|
||||||
|
export const useGuide = (options?: Config) => {
|
||||||
|
const driverObj = driver(
|
||||||
|
options || {
|
||||||
|
showProgress: true,
|
||||||
|
nextBtnText: t('common.nextLabel'),
|
||||||
|
prevBtnText: t('common.prevLabel'),
|
||||||
|
doneBtnText: t('common.doneLabel'),
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
element: `#${variables.namespace}-menu`,
|
||||||
|
popover: {
|
||||||
|
title: t('common.menu'),
|
||||||
|
description: t('common.menuDes'),
|
||||||
|
side: 'right'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: `#${variables.namespace}-tool-header`,
|
||||||
|
popover: {
|
||||||
|
title: t('common.tool'),
|
||||||
|
description: t('common.toolDes'),
|
||||||
|
side: 'left'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: `#${variables.namespace}-tags-view`,
|
||||||
|
popover: {
|
||||||
|
title: t('common.tagsView'),
|
||||||
|
description: t('common.tagsViewDes'),
|
||||||
|
side: 'bottom'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...driverObj
|
||||||
|
}
|
||||||
|
}
|
@ -1,47 +0,0 @@
|
|||||||
import introJs from 'intro.js'
|
|
||||||
import { IntroJs, Step, Options } from 'intro.js'
|
|
||||||
import 'intro.js/introjs.css'
|
|
||||||
|
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
|
||||||
|
|
||||||
export const useIntro = (setps?: Step[], options?: Options) => {
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const { variables } = useDesign()
|
|
||||||
|
|
||||||
const defaultSetps: Step[] = setps || [
|
|
||||||
{
|
|
||||||
element: `#${variables.namespace}-menu`,
|
|
||||||
title: t('common.menu'),
|
|
||||||
intro: t('common.menuDes'),
|
|
||||||
position: 'right'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
element: `#${variables.namespace}-tool-header`,
|
|
||||||
title: t('common.tool'),
|
|
||||||
intro: t('common.toolDes'),
|
|
||||||
position: 'left'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
element: `#${variables.namespace}-tags-view`,
|
|
||||||
title: t('common.tagsView'),
|
|
||||||
intro: t('common.tagsViewDes'),
|
|
||||||
position: 'bottom'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const defaultOptions: Options = options || {
|
|
||||||
prevLabel: t('common.prevLabel'),
|
|
||||||
nextLabel: t('common.nextLabel'),
|
|
||||||
skipLabel: t('common.skipLabel'),
|
|
||||||
doneLabel: t('common.doneLabel')
|
|
||||||
}
|
|
||||||
|
|
||||||
const introRef: IntroJs = introJs()
|
|
||||||
|
|
||||||
introRef.addSteps(defaultSetps).setOptions(defaultOptions)
|
|
||||||
|
|
||||||
return {
|
|
||||||
introRef
|
|
||||||
}
|
|
||||||
}
|
|
21
src/hooks/web/useNetwork.ts
Normal file
21
src/hooks/web/useNetwork.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { ref, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
|
const useNetwork = () => {
|
||||||
|
const online = ref(true)
|
||||||
|
|
||||||
|
const updateNetwork = () => {
|
||||||
|
online.value = navigator.onLine
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('online', updateNetwork)
|
||||||
|
window.addEventListener('offline', updateNetwork)
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('online', updateNetwork)
|
||||||
|
window.removeEventListener('offline', updateNetwork)
|
||||||
|
})
|
||||||
|
|
||||||
|
return { online }
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useNetwork }
|
60
src/hooks/web/useNow.ts
Normal file
60
src/hooks/web/useNow.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { dateUtil } from '@/utils/dateUtil'
|
||||||
|
import { reactive, toRefs } from 'vue'
|
||||||
|
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'
|
||||||
|
|
||||||
|
export const useNow = (immediate = true) => {
|
||||||
|
let timer: IntervalHandle
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
year: 0,
|
||||||
|
month: 0,
|
||||||
|
week: '',
|
||||||
|
day: 0,
|
||||||
|
hour: '',
|
||||||
|
minute: '',
|
||||||
|
second: 0,
|
||||||
|
meridiem: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
const now = dateUtil()
|
||||||
|
|
||||||
|
const h = now.format('HH')
|
||||||
|
const m = now.format('mm')
|
||||||
|
const s = now.get('s')
|
||||||
|
|
||||||
|
state.year = now.get('y')
|
||||||
|
state.month = now.get('M') + 1
|
||||||
|
state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]
|
||||||
|
state.day = now.get('date')
|
||||||
|
state.hour = h
|
||||||
|
state.minute = m
|
||||||
|
state.second = s
|
||||||
|
|
||||||
|
state.meridiem = now.format('A')
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
update()
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = setInterval(() => update(), 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
tryOnMounted(() => {
|
||||||
|
immediate && start()
|
||||||
|
})
|
||||||
|
|
||||||
|
tryOnUnmounted(() => {
|
||||||
|
stop()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
start,
|
||||||
|
stop
|
||||||
|
}
|
||||||
|
}
|
63
src/hooks/web/useTagsView.ts
Normal file
63
src/hooks/web/useTagsView.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { useTagsViewStoreWithOut } from '@/store/modules/tagsView'
|
||||||
|
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router'
|
||||||
|
import { computed, nextTick, unref } from 'vue'
|
||||||
|
|
||||||
|
export const useTagsView = () => {
|
||||||
|
const tagsViewStore = useTagsViewStoreWithOut()
|
||||||
|
|
||||||
|
const { replace, currentRoute } = useRouter()
|
||||||
|
|
||||||
|
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
|
||||||
|
|
||||||
|
const closeAll = (callback?: Fn) => {
|
||||||
|
tagsViewStore.delAllViews()
|
||||||
|
callback?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeLeft = (callback?: Fn) => {
|
||||||
|
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||||
|
callback?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeRight = (callback?: Fn) => {
|
||||||
|
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||||
|
callback?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeOther = (callback?: Fn) => {
|
||||||
|
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||||
|
callback?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeCurrent = (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
|
||||||
|
if (view?.meta?.affix) return
|
||||||
|
tagsViewStore.delView(view || unref(currentRoute))
|
||||||
|
|
||||||
|
callback?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshPage = async (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
|
||||||
|
tagsViewStore.delCachedView()
|
||||||
|
const { path, query } = view || unref(currentRoute)
|
||||||
|
await nextTick()
|
||||||
|
replace({
|
||||||
|
path: '/redirect' + path,
|
||||||
|
query: query
|
||||||
|
})
|
||||||
|
callback?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTitle = (title: string, path?: string) => {
|
||||||
|
tagsViewStore.setTitle(title, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
closeAll,
|
||||||
|
closeLeft,
|
||||||
|
closeRight,
|
||||||
|
closeOther,
|
||||||
|
closeCurrent,
|
||||||
|
refreshPage,
|
||||||
|
setTitle
|
||||||
|
}
|
||||||
|
}
|
@ -1,54 +1,53 @@
|
|||||||
const { t } = useI18n()
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { FormItemRule } from 'element-plus'
|
||||||
|
|
||||||
type Callback = (error?: string | Error | undefined) => void
|
const { t } = useI18n()
|
||||||
|
|
||||||
interface LengthRange {
|
interface LengthRange {
|
||||||
min: number
|
min: number
|
||||||
max: number
|
max: number
|
||||||
message: string
|
message?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useValidator = () => {
|
export const useValidator = () => {
|
||||||
const required = (message?: string) => {
|
const required = (message?: string): FormItemRule => {
|
||||||
return {
|
return {
|
||||||
required: true,
|
required: true,
|
||||||
message: message || t('common.required')
|
message: message || t('common.required')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const lengthRange = (val: any, callback: Callback, options: LengthRange) => {
|
const lengthRange = (options: LengthRange): FormItemRule => {
|
||||||
const { min, max, message } = options
|
const { min, max, message } = options
|
||||||
if (val.length < min || val.length > max) {
|
|
||||||
callback(new Error(message))
|
return {
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
message: message || t('common.lengthRange', { min, max })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notSpace = (message?: string): FormItemRule => {
|
||||||
|
return {
|
||||||
|
validator: (_, val, callback) => {
|
||||||
|
if (val?.indexOf(' ') !== -1) {
|
||||||
|
callback(new Error(message || t('common.notSpace')))
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notSpace = (val: any, callback: Callback, message: string) => {
|
|
||||||
// 用户名不能有空格
|
|
||||||
if (val.indexOf(' ') !== -1) {
|
|
||||||
callback(new Error(message))
|
|
||||||
} else {
|
|
||||||
callback()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notSpecialCharacters = (val: any, callback: Callback, message: string) => {
|
const notSpecialCharacters = (message?: string): FormItemRule => {
|
||||||
// 密码不能是特殊字符
|
return {
|
||||||
|
validator: (_, val, callback) => {
|
||||||
if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
|
if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
|
||||||
callback(new Error(message))
|
callback(new Error(message || t('common.notSpecialCharacters')))
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 两个字符串是否想等
|
|
||||||
const isEqual = (val1: string, val2: string, callback: Callback, message: string) => {
|
|
||||||
if (val1 === val2) {
|
|
||||||
callback()
|
|
||||||
} else {
|
|
||||||
callback(new Error(message))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +55,6 @@ export const useValidator = () => {
|
|||||||
required,
|
required,
|
||||||
lengthRange,
|
lengthRange,
|
||||||
notSpace,
|
notSpace,
|
||||||
notSpecialCharacters,
|
notSpecialCharacters
|
||||||
isEqual
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
<ElTabs v-model="activeName">
|
<ElTabs v-model="activeName">
|
||||||
<ElTabPane label="我的站内信" name="notice">
|
<ElTabPane label="我的站内信" name="notice">
|
||||||
<div class="message-list">
|
<el-scrollbar class="message-list">
|
||||||
<template v-for="item in list" :key="item.id">
|
<template v-for="item in list" :key="item.id">
|
||||||
<div class="message-item">
|
<div class="message-item">
|
||||||
<img alt="" class="message-icon" src="@/assets/imgs/avatar.gif" />
|
<img alt="" class="message-icon" src="@/assets/imgs/avatar.gif" />
|
||||||
@ -67,7 +67,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</el-scrollbar>
|
||||||
</ElTabPane>
|
</ElTabPane>
|
||||||
</ElTabs>
|
</ElTabs>
|
||||||
<!-- 更多 -->
|
<!-- 更多 -->
|
||||||
@ -88,6 +88,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message-list {
|
.message-list {
|
||||||
|
height: 400px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
@ -116,6 +116,7 @@ export enum DICT_TYPE {
|
|||||||
SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',
|
SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',
|
||||||
SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
|
SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
|
||||||
SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',
|
SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',
|
||||||
|
SYSTEM_SOCIAL_TYPE = 'system_social_type',
|
||||||
|
|
||||||
// ========== INFRA 模块 ==========
|
// ========== INFRA 模块 ==========
|
||||||
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
|
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
|
||||||
@ -193,5 +194,6 @@ export enum DICT_TYPE {
|
|||||||
CRM_RETURN_TYPE = 'crm_return_type',
|
CRM_RETURN_TYPE = 'crm_return_type',
|
||||||
CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry',
|
CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry',
|
||||||
CRM_CUSTOMER_LEVEL = 'crm_customer_level',
|
CRM_CUSTOMER_LEVEL = 'crm_customer_level',
|
||||||
CRM_CUSTOMER_SOURCE = 'crm_customer_source'
|
CRM_CUSTOMER_SOURCE = 'crm_customer_source',
|
||||||
|
CRM_PRODUCT_STATUS = 'crm_product_status'
|
||||||
}
|
}
|
||||||
|
@ -334,6 +334,6 @@ export function getLast1Year(): [dayjs.ConfigType, dayjs.ConfigType] {
|
|||||||
export function getDateRange(
|
export function getDateRange(
|
||||||
beginDate: dayjs.ConfigType,
|
beginDate: dayjs.ConfigType,
|
||||||
endDate: dayjs.ConfigType
|
endDate: dayjs.ConfigType
|
||||||
): [dayjs.ConfigType, dayjs.ConfigType] {
|
): [string, string] {
|
||||||
return [dayjs(beginDate).startOf('d'), dayjs(endDate).endOf('d')]
|
return [dayjs(beginDate).startOf('d').toString(), dayjs(endDate).endOf('d').toString()]
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,13 @@ export const underlineToHump = (str: string): string => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驼峰转横杠
|
||||||
|
*/
|
||||||
|
export const humpToDash = (str: string): string => {
|
||||||
|
return str.replace(/([A-Z])/g, '-$1').toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
export const setCssVar = (prop: string, val: any, dom = document.documentElement) => {
|
export const setCssVar = (prop: string, val: any, dom = document.documentElement) => {
|
||||||
dom.style.setProperty(prop, val)
|
dom.style.setProperty(prop, val)
|
||||||
}
|
}
|
||||||
@ -67,7 +74,7 @@ export const trim = (str: string) => {
|
|||||||
* @param {Date | number | string} time 需要转换的时间
|
* @param {Date | number | string} time 需要转换的时间
|
||||||
* @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss
|
* @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss
|
||||||
*/
|
*/
|
||||||
export const formatTime = (time: Date | number | string, fmt: string) => {
|
export function formatTime(time: Date | number | string, fmt: string) {
|
||||||
if (!time) return ''
|
if (!time) return ''
|
||||||
else {
|
else {
|
||||||
const date = new Date(time)
|
const date = new Date(time)
|
||||||
@ -98,7 +105,7 @@ export const formatTime = (time: Date | number | string, fmt: string) => {
|
|||||||
/**
|
/**
|
||||||
* 生成随机字符串
|
* 生成随机字符串
|
||||||
*/
|
*/
|
||||||
export const toAnyString = () => {
|
export function toAnyString() {
|
||||||
const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {
|
const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {
|
||||||
const r: number = (Math.random() * 16) | 0
|
const r: number = (Math.random() * 16) | 0
|
||||||
const v: number = c === 'x' ? r : (r & 0x3) | 0x8
|
const v: number = c === 'x' ? r : (r & 0x3) | 0x8
|
||||||
@ -107,6 +114,13 @@ export const toAnyString = () => {
|
|||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首字母大写
|
||||||
|
*/
|
||||||
|
export function firstUpperCase(str: string) {
|
||||||
|
return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
|
||||||
|
}
|
||||||
|
|
||||||
export const generateUUID = () => {
|
export const generateUUID = () => {
|
||||||
if (typeof crypto === 'object') {
|
if (typeof crypto === 'object') {
|
||||||
if (typeof crypto.randomUUID === 'function') {
|
if (typeof crypto.randomUUID === 'function') {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div>
|
<div>
|
||||||
<el-tabs v-model="activeName" tab-position="top" style="height: 400px" class="profile-tabs">
|
<el-tabs v-model="activeName" class="profile-tabs" style="height: 400px" tab-position="top">
|
||||||
<el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo">
|
<el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo">
|
||||||
<BasicInfo />
|
<BasicInfo />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@ -23,17 +23,18 @@
|
|||||||
<ResetPwd />
|
<ResetPwd />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="t('profile.info.userSocial')" name="userSocial">
|
<el-tab-pane :label="t('profile.info.userSocial')" name="userSocial">
|
||||||
<UserSocial />
|
<UserSocial v-model:activeName="activeName" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="Profile">
|
<script lang="ts" setup>
|
||||||
import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components/'
|
import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
defineOptions({ name: 'Profile' })
|
||||||
const activeName = ref('basicInfo')
|
const activeName = ref('basicInfo')
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -27,12 +27,15 @@ import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
|||||||
import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'
|
import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'
|
||||||
|
|
||||||
defineOptions({ name: 'UserSocial' })
|
defineOptions({ name: 'UserSocial' })
|
||||||
|
defineProps<{
|
||||||
|
activeName: string
|
||||||
|
}>()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const socialUsers = ref<any[]>([])
|
const socialUsers = ref<any[]>([])
|
||||||
const userInfo = ref<ProfileVO>()
|
const userInfo = ref<ProfileVO>()
|
||||||
|
|
||||||
const initSocial = async () => {
|
const initSocial = async () => {
|
||||||
|
socialUsers.value = [] // 重置避免无限增长
|
||||||
const res = await getUserProfile()
|
const res = await getUserProfile()
|
||||||
userInfo.value = res
|
userInfo.value = res
|
||||||
for (const i in SystemUserSocialTypeEnum) {
|
for (const i in SystemUserSocialTypeEnum) {
|
||||||
@ -49,9 +52,12 @@ const initSocial = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:activeName', v: string): void
|
||||||
|
}>()
|
||||||
const bindSocial = () => {
|
const bindSocial = () => {
|
||||||
// 社交绑定
|
// 社交绑定
|
||||||
const type = route.query.type
|
const type = getUrlValue('type')
|
||||||
const code = route.query.code
|
const code = route.query.code
|
||||||
const state = route.query.state
|
const state = route.query.state
|
||||||
if (!code) {
|
if (!code) {
|
||||||
@ -59,11 +65,20 @@ const bindSocial = () => {
|
|||||||
}
|
}
|
||||||
socialBind(type, code, state).then(() => {
|
socialBind(type, code, state).then(() => {
|
||||||
message.success('绑定成功')
|
message.success('绑定成功')
|
||||||
|
emit('update:activeName', 'userSocial')
|
||||||
initSocial()
|
initSocial()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 双层 encode 需要在回调后进行 decode
|
||||||
|
function getUrlValue(key: string): string {
|
||||||
|
const url = new URL(decodeURIComponent(location.href))
|
||||||
|
return url.searchParams.get(key) ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
const bind = (row) => {
|
const bind = (row) => {
|
||||||
const redirectUri = location.origin + '/user/profile?type=' + row.type
|
// 双层 encode 解决钉钉回调 type 参数丢失的问题
|
||||||
|
const redirectUri = location.origin + '/user/profile?' + encodeURIComponent(`type=${row.type}`)
|
||||||
// 进行跳转
|
// 进行跳转
|
||||||
socialAuthRedirect(row.type, encodeURIComponent(redirectUri)).then((res) => {
|
socialAuthRedirect(row.type, encodeURIComponent(redirectUri)).then((res) => {
|
||||||
window.location.href = res
|
window.location.href = res
|
||||||
@ -83,9 +98,8 @@ onMounted(async () => {
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route,
|
() => route,
|
||||||
(newRoute) => {
|
() => {
|
||||||
bindSocial()
|
bindSocial()
|
||||||
console.log(newRoute)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true
|
immediate: true
|
||||||
|
70
src/views/crm/product/ProductDetail.vue
Normal file
70
src/views/crm/product/ProductDetail.vue
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" :max-height="500" :scroll="true" title="产品详情">
|
||||||
|
<el-descriptions :column="1" border>
|
||||||
|
<el-descriptions-item label="产品名称">
|
||||||
|
{{ detailData.name }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建时间">
|
||||||
|
{{ formatDate(detailData.createTime) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="状态">
|
||||||
|
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_STATUS" :value="detailData.status" />
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品分类">
|
||||||
|
{{ productCategoryList?.find((c) => c.id === detailData.categoryId)?.name }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品编码">
|
||||||
|
{{ detailData.no }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品描述">
|
||||||
|
{{ detailData.description }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="负责人">
|
||||||
|
{{ detailData.ownerUserId }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="单位">
|
||||||
|
<dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="detailData.unit" />
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="价格">
|
||||||
|
{{ fenToYuan(detailData.price) }}元
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import * as ProductCategoryApi from '@/api/crm/productCategory'
|
||||||
|
import * as ProductApi from '@/api/crm/product'
|
||||||
|
import { formatDate } from '@/utils/formatTime'
|
||||||
|
import { fenToYuan } from '@/utils'
|
||||||
|
import { getSimpleUserList, UserVO } from '@/api/system/user'
|
||||||
|
|
||||||
|
defineOptions({ name: 'CrmProductDetail' })
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const detailLoading = ref(false) // 表单的加载中
|
||||||
|
const detailData = ref() // 详情数据
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (data: ProductApi.ProductVO) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
// 设置数据
|
||||||
|
detailLoading.value = true
|
||||||
|
try {
|
||||||
|
detailData.value = data
|
||||||
|
} finally {
|
||||||
|
detailLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
const productCategoryList = ref([]) // 产品分类树
|
||||||
|
const userList = ref<UserVO[]>([]) // 系统用户
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
productCategoryList.value = await ProductCategoryApi.getProductCategoryList({})
|
||||||
|
userList.value = await getSimpleUserList()
|
||||||
|
})
|
||||||
|
</script>
|
185
src/views/crm/product/ProductForm.vue
Normal file
185
src/views/crm/product/ProductForm.vue
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<!-- TODO @zange:改成每行两个哈; -->
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="100px"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<el-form-item label="产品名称" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入产品名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="产品编码" prop="no">
|
||||||
|
<el-input v-model="formData.no" placeholder="请输入产品编码" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="单位" prop="unit">
|
||||||
|
<el-select v-model="formData.unit" class="w-1/1" placeholder="请选择单位">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="价格" prop="price">
|
||||||
|
<el-input type="number" v-model="formData.price" placeholder="请输入价格" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-select v-model="formData.status" placeholder="请选择状态">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PRODUCT_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="产品分类" prop="categoryId">
|
||||||
|
<el-cascader
|
||||||
|
v-model="formData.categoryId"
|
||||||
|
:options="productCategoryList"
|
||||||
|
:props="defaultProps"
|
||||||
|
class="w-1/1"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择产品分类"
|
||||||
|
filterable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="产品描述" prop="description">
|
||||||
|
<el-input v-model="formData.description" placeholder="请输入产品描述" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="负责人" prop="ownerUserId">
|
||||||
|
<el-select
|
||||||
|
v-model="formData.ownerUserId"
|
||||||
|
placeholder="请选择负责人"
|
||||||
|
:disabled="formData.id"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="user in userList"
|
||||||
|
:key="user.id"
|
||||||
|
:label="user.nickname"
|
||||||
|
:value="user.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import * as ProductApi from '@/api/crm/product'
|
||||||
|
import * as ProductCategoryApi from '@/api/crm/productCategory'
|
||||||
|
import { defaultProps, handleTree } from '@/utils/tree'
|
||||||
|
import { getSimpleUserList, UserVO } from '@/api/system/user'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
|
defineOptions({ name: 'CrmProductForm' })
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
const userId = useUserStore().getUser.id // 当前登录的编号
|
||||||
|
const formData = ref({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
no: undefined,
|
||||||
|
unit: undefined,
|
||||||
|
price: undefined,
|
||||||
|
status: undefined,
|
||||||
|
categoryId: undefined,
|
||||||
|
description: undefined,
|
||||||
|
ownerUserId: undefined
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
name: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }],
|
||||||
|
no: [{ required: true, message: '产品编码不能为空', trigger: 'blur' }],
|
||||||
|
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||||
|
categoryId: [{ required: true, message: '产品分类ID不能为空', trigger: 'blur' }],
|
||||||
|
ownerUserId: [{ required: true, message: '负责人不能为空', trigger: 'blur' }],
|
||||||
|
unit: [{ required: true, message: '单位不能为空', trigger: 'blur' }],
|
||||||
|
price: [{ required: true, message: '价格不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
formData.value.ownerUserId = userId
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
formData.value = await ProductApi.getProduct(id)
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单
|
||||||
|
if (!formRef) return
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = formData.value as unknown as ProductApi.ProductVO
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await ProductApi.createProduct(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await ProductApi.updateProduct(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
no: undefined,
|
||||||
|
unit: undefined,
|
||||||
|
price: undefined,
|
||||||
|
status: undefined,
|
||||||
|
categoryId: undefined,
|
||||||
|
description: undefined,
|
||||||
|
ownerUserId: undefined
|
||||||
|
}
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
const productCategoryList = ref<any[]>([]) // 产品分类树
|
||||||
|
const userList = ref<UserVO[]>([]) // 系统用户
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const data = await ProductCategoryApi.getProductCategoryList({})
|
||||||
|
productCategoryList.value = handleTree(data, 'id', 'parentId')
|
||||||
|
userList.value = await getSimpleUserList()
|
||||||
|
})
|
||||||
|
</script>
|
269
src/views/crm/product/index.vue
Normal file
269
src/views/crm/product/index.vue
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<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="no">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.no"
|
||||||
|
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">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getBoolDictOptions(DICT_TYPE.CRM_PRODUCT_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="产品分类" prop="categoryId">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.categoryId"
|
||||||
|
placeholder="请选择产品分类"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</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" @click="openForm('create')" v-hasPermi="['crm:product:create']">
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['crm:product: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="主键id" align="center" prop="id" />-->
|
||||||
|
<el-table-column label="产品名称" align="center" prop="name" />
|
||||||
|
<el-table-column label="产品编码" align="center" prop="no" />
|
||||||
|
<el-table-column label="单位" align="center" prop="unit">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="scope.row.unit" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="价格" align="center" prop="price">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ fenToYuan(row.price) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" align="center" prop="status">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_STATUS" :value="scope.row.status" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="产品分类" align="center" prop="categoryId">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{ productCategoryList?.find((c) => c.id === row.categoryId)?.name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="产品描述" align="center" prop="description" />
|
||||||
|
<el-table-column label="负责人" align="center" prop="ownerUserId">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{ userList?.find((c) => c.id === row.ownerUserId)?.nickname }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="创建时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="180px"
|
||||||
|
/>
|
||||||
|
<el-table-column label="操作" align="center" width="160">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['crm:product:query']"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openDetail(scope.row)"
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['crm:product:update']"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['crm:product: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>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<ProductForm ref="formRef" @success="getList" />
|
||||||
|
|
||||||
|
<!-- 表单弹窗:详情 -->
|
||||||
|
<ProductDetail ref="detailRef" @success="getList" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
import * as ProductApi from '@/api/crm/product'
|
||||||
|
import ProductForm from './ProductForm.vue'
|
||||||
|
import ProductDetail from './ProductDetail.vue'
|
||||||
|
import { fenToYuan } from '@/utils'
|
||||||
|
import * as ProductCategoryApi from '@/api/crm/productCategory'
|
||||||
|
import { getSimpleUserList, UserVO } from '@/api/system/user'
|
||||||
|
|
||||||
|
defineOptions({ name: 'CrmProduct' })
|
||||||
|
|
||||||
|
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,
|
||||||
|
no: null,
|
||||||
|
unit: null,
|
||||||
|
price: null,
|
||||||
|
status: null,
|
||||||
|
categoryId: null,
|
||||||
|
description: null,
|
||||||
|
ownerUserId: null,
|
||||||
|
createTime: []
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await ProductApi.getProductPage(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()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
/** 详情操作 */
|
||||||
|
const detailRef = ref()
|
||||||
|
const openDetail = (data: ProductApi.ProductVO) => {
|
||||||
|
detailRef.value.open(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await ProductApi.deleteProduct(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await message.exportConfirm()
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await ProductApi.exportProduct(queryParams)
|
||||||
|
download.excel(data, '产品.xls')
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const productCategoryList = ref([]) // 产品分类树
|
||||||
|
const userList = ref<UserVO[]>([]) // 系统用户
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(async () => {
|
||||||
|
await getList()
|
||||||
|
productCategoryList.value = await ProductCategoryApi.getProductCategoryList({})
|
||||||
|
userList.value = await getSimpleUserList()
|
||||||
|
})
|
||||||
|
</script>
|
110
src/views/crm/productCategory/ProductCategoryForm.vue
Normal file
110
src/views/crm/productCategory/ProductCategoryForm.vue
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="100px"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<el-form-item label="父级id" prop="parentId">
|
||||||
|
<el-select v-model="formData.parentId" placeholder="请选择上级分类">
|
||||||
|
<el-option :key="0" label="顶级分类" :value="0" />
|
||||||
|
<el-option
|
||||||
|
v-for="item in productCategoryList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="名称" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import * as ProductCategoryApi from '@/api/crm/productCategory'
|
||||||
|
|
||||||
|
defineOptions({ name: 'CrmProductCategoryForm' })
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
const formData = ref({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
parentId: undefined
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
|
||||||
|
parentId: [{ required: true, message: '父级分类不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
const productCategoryList = ref<any[]>([]) // 产品分类树
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
formData.value = await ProductCategoryApi.getProductCategory(id)
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 获得分类树
|
||||||
|
productCategoryList.value = await ProductCategoryApi.getProductCategoryList({ parentId: 0 })
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单
|
||||||
|
if (!formRef) return
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = formData.value as unknown as ProductCategoryApi.ProductCategoryVO
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await ProductCategoryApi.createProductCategory(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await ProductCategoryApi.updateProductCategory(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
parentId: undefined
|
||||||
|
}
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
</script>
|
138
src/views/crm/productCategory/index.vue
Normal file
138
src/views/crm/productCategory/index.vue
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<!-- TODO @zange:挪到 product 下,建个 category 包,挪进去哈; -->
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<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>
|
||||||
|
<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="['crm:product-category:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" row-key="id" default-expand-all>
|
||||||
|
<el-table-column label="名称" align="center" prop="name" />
|
||||||
|
<el-table-column
|
||||||
|
label="创建时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="180px"
|
||||||
|
/>
|
||||||
|
<el-table-column label="操作" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['crm:product-category:update']"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['crm:product-category:delete']"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<ProductCategoryForm ref="formRef" @success="getList" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
import * as ProductCategoryApi from '@/api/crm/productCategory'
|
||||||
|
import ProductCategoryForm from './ProductCategoryForm.vue'
|
||||||
|
import { handleTree } from '@/utils/tree'
|
||||||
|
|
||||||
|
defineOptions({ name: 'CrmProductCategory' })
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref<any[]>([]) // 列表的数据
|
||||||
|
const queryParams = reactive({
|
||||||
|
name: null
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await ProductCategoryApi.getProductCategoryList(queryParams)
|
||||||
|
list.value = handleTree(data, 'id', 'parentId')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加/修改操作 */
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await ProductCategoryApi.deleteProductCategory(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
@ -10,14 +10,14 @@
|
|||||||
<el-form-item label="回款编号" prop="no">
|
<el-form-item label="回款编号" prop="no">
|
||||||
<el-input v-model="formData.no" placeholder="请输入回款编号" />
|
<el-input v-model="formData.no" placeholder="请输入回款编号" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!--<el-form-item label="回款计划ID" prop="planId">
|
<el-form-item label="回款计划" prop="planId">
|
||||||
<el-input v-model="formData.planId" placeholder="请输入回款计划ID" />
|
<el-input v-model="formData.planId" placeholder="请输入回款计划" />
|
||||||
</el-form-item>-->
|
|
||||||
<el-form-item label="客户ID" prop="customerId">
|
|
||||||
<el-input v-model="formData.customerId" placeholder="请输入客户ID" />
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="合同ID" prop="contractId">
|
<el-form-item label="客户名称" prop="customerId">
|
||||||
<el-input v-model="formData.contractId" placeholder="请输入合同ID" />
|
<el-input v-model="formData.customerId" placeholder="请输入客户名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="合同名称" prop="contractId">
|
||||||
|
<el-input v-model="formData.contractId" placeholder="请输入合同名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!--<el-form-item label="审批状态" prop="checkStatus">
|
<!--<el-form-item label="审批状态" prop="checkStatus">
|
||||||
<el-select v-model="formData.checkStatus" placeholder="请选择审批状态">
|
<el-select v-model="formData.checkStatus" placeholder="请选择审批状态">
|
||||||
@ -54,15 +54,22 @@
|
|||||||
<el-input-number v-model="formData.price" placeholder="请输入回款金额" />
|
<el-input-number v-model="formData.price" placeholder="请输入回款金额" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="负责人" prop="ownerUserId">
|
<el-form-item label="负责人" prop="ownerUserId">
|
||||||
<el-input v-model="formData.ownerUserId" placeholder="请输入负责人" />
|
<el-select v-model="formData.ownerUserId" clearable placeholder="请输入负责人">
|
||||||
|
<el-option
|
||||||
|
v-for="item in userList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.nickname"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="批次" prop="batchId">
|
<el-form-item label="批次" prop="batchId">
|
||||||
<el-input v-model="formData.batchId" placeholder="请输入批次" />
|
<el-input-number v-model="formData.batchId" placeholder="请输入批次" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!--<el-form-item label="显示顺序" prop="sort">
|
<el-form-item label="显示排序" prop="sort">
|
||||||
<el-input v-model="formData.sort" placeholder="请输入显示顺序" />
|
<el-input-number v-model="formData.sort" :min="0" controls-position="right" />
|
||||||
</el-form-item>-->
|
</el-form-item>
|
||||||
<el-form-item label="状态" prop="status">
|
<!--<el-form-item label="状态" prop="status">
|
||||||
<el-select v-model="formData.status" placeholder="请选择状态">
|
<el-select v-model="formData.status" placeholder="请选择状态">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
@ -71,7 +78,7 @@
|
|||||||
:value="dict.value"
|
:value="dict.value"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>-->
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
<el-input type="textarea" :rows="3" v-model="formData.remark" placeholder="请输入备注" />
|
<el-input type="textarea" :rows="3" v-model="formData.remark" placeholder="请输入备注" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -85,10 +92,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
||||||
import * as ReceivableApi from '@/api/crm/receivable'
|
import * as ReceivableApi from '@/api/crm/receivable'
|
||||||
|
import * as UserApi from '@/api/system/user'
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
@ -112,9 +120,9 @@ const formData = ref({
|
|||||||
status: undefined,
|
status: undefined,
|
||||||
remark: undefined
|
remark: undefined
|
||||||
})
|
})
|
||||||
const formRules = reactive({
|
// const formRules = reactive({
|
||||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
// status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||||
})
|
// })
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
@ -132,6 +140,8 @@ const open = async (type: string, id?: number) => {
|
|||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 获得用户列表
|
||||||
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
@ -26,19 +26,19 @@
|
|||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>-->
|
</el-form-item>-->
|
||||||
<el-form-item label="客户" prop="customerId">
|
<el-form-item label="客户名称" prop="customerId">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.customerId"
|
v-model="queryParams.customerId"
|
||||||
placeholder="请输入客户"
|
placeholder="请输入客户名称"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter="handleQuery"
|
@keyup.enter="handleQuery"
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="合同" prop="contractId">
|
<el-form-item label="合同名称" prop="contractId">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.contractId"
|
v-model="queryParams.contractId"
|
||||||
placeholder="请输入合同"
|
placeholder="请输入合同名称"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter="handleQuery"
|
@keyup.enter="handleQuery"
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
@ -103,7 +103,7 @@
|
|||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>-->
|
</el-form-item>-->
|
||||||
<el-form-item label="负责人" prop="ownerUserId">
|
<!--<el-form-item label="负责人" prop="ownerUserId">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.ownerUserId"
|
v-model="queryParams.ownerUserId"
|
||||||
placeholder="请输入负责人"
|
placeholder="请输入负责人"
|
||||||
@ -112,7 +112,7 @@
|
|||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!--<el-form-item label="批次" prop="batchId">
|
<el-form-item label="批次" prop="batchId">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.batchId"
|
v-model="queryParams.batchId"
|
||||||
placeholder="请输入批次"
|
placeholder="请输入批次"
|
||||||
@ -227,8 +227,12 @@
|
|||||||
:formatter="dateFormatter"
|
:formatter="dateFormatter"
|
||||||
width="180px"
|
width="180px"
|
||||||
/>
|
/>
|
||||||
<el-table-column label="操作" align="center" width="130px">
|
<el-table-column label="操作" align="center" width="180px">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
|
<!-- todo @liuhongfeng:用路径参数哈,receivableId -->
|
||||||
|
<!--<router-link :to="'/crm/receivable-plan?receivableId=' + scope.row.receivableId">
|
||||||
|
<el-button link type="primary">详情</el-button>
|
||||||
|
</router-link>-->
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
|
@ -7,8 +7,24 @@
|
|||||||
label-width="100px"
|
label-width="100px"
|
||||||
v-loading="formLoading"
|
v-loading="formLoading"
|
||||||
>
|
>
|
||||||
<el-form-item label="期数" prop="indexNo">
|
<el-form-item label="客户名称" prop="customerId">
|
||||||
<el-input-number v-model="formData.indexNo" placeholder="请输入期数" />
|
<el-input v-model="formData.customerId" placeholder="请输入客户名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="合同名称" prop="contractId">
|
||||||
|
<el-input v-model="formData.contractId" placeholder="请输入合同名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="负责人" prop="ownerUserId">
|
||||||
|
<el-select v-model="formData.ownerUserId" clearable placeholder="请输入负责人">
|
||||||
|
<el-option
|
||||||
|
v-for="item in userList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.nickname"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="期数" prop="period">
|
||||||
|
<el-input-number v-model="formData.period" placeholder="请输入期数" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!--<el-form-item label="回款ID" prop="receivableId">
|
<!--<el-form-item label="回款ID" prop="receivableId">
|
||||||
<el-input v-model="formData.receivableId" placeholder="请输入回款ID" />
|
<el-input v-model="formData.receivableId" placeholder="请输入回款ID" />
|
||||||
@ -58,18 +74,9 @@
|
|||||||
placeholder="选择提醒日期"
|
placeholder="选择提醒日期"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="客户ID" prop="customerId">
|
<el-form-item label="显示排序" prop="sort">
|
||||||
<el-input v-model="formData.customerId" placeholder="请输入客户ID" />
|
<el-input-number v-model="formData.sort" :min="0" controls-position="right" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="合同ID" prop="contractId">
|
|
||||||
<el-input v-model="formData.contractId" placeholder="请输入合同ID" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="负责人" prop="ownerUserId">
|
|
||||||
<el-input v-model="formData.ownerUserId" placeholder="请输入负责人" />
|
|
||||||
</el-form-item>
|
|
||||||
<!--<el-form-item label="显示顺序" prop="sort">
|
|
||||||
<el-input v-model="formData.sort" placeholder="请输入显示顺序" />
|
|
||||||
</el-form-item>-->
|
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
<el-input type="textarea" :rows="3" v-model="formData.remark" placeholder="请输入备注" />
|
<el-input type="textarea" :rows="3" v-model="formData.remark" placeholder="请输入备注" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -81,19 +88,18 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
|
||||||
import * as ReceivablePlanApi from '@/api/crm/receivablePlan'
|
import * as ReceivablePlanApi from '@/api/crm/receivablePlan'
|
||||||
|
import * as UserApi from '@/api/system/user'
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
indexNo: undefined,
|
period: undefined,
|
||||||
receivableId: undefined,
|
receivableId: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
checkStatus: undefined,
|
checkStatus: undefined,
|
||||||
@ -128,6 +134,9 @@ const open = async (type: string, id?: number) => {
|
|||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获得用户列表
|
||||||
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
@ -161,7 +170,7 @@ const submitForm = async () => {
|
|||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
indexNo: undefined,
|
period: undefined,
|
||||||
receivableId: undefined,
|
receivableId: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
checkStatus: undefined,
|
checkStatus: undefined,
|
||||||
|
@ -8,10 +8,19 @@
|
|||||||
:inline="true"
|
:inline="true"
|
||||||
label-width="68px"
|
label-width="68px"
|
||||||
>
|
>
|
||||||
<el-form-item label="期数" prop="indexNo">
|
<el-form-item label="客户" prop="customerId">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.indexNo"
|
v-model="queryParams.customerId"
|
||||||
placeholder="请输入期数"
|
placeholder="请输入客户"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="合同" prop="contractId">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.contractId"
|
||||||
|
placeholder="请输入合同"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter="handleQuery"
|
@keyup.enter="handleQuery"
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
@ -67,7 +76,7 @@
|
|||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>-->
|
</el-form-item>-->
|
||||||
<el-form-item label="提醒日期" prop="remindTime">
|
<!--<el-form-item label="提醒日期" prop="remindTime">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="queryParams.remindTime"
|
v-model="queryParams.remindTime"
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
@ -77,26 +86,8 @@
|
|||||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>-->
|
||||||
<el-form-item label="客户" prop="customerId">
|
<!--<el-form-item label="负责人" prop="ownerUserId">
|
||||||
<el-input
|
|
||||||
v-model="queryParams.customerId"
|
|
||||||
placeholder="请输入客户"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="合同" prop="contractId">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.contractId"
|
|
||||||
placeholder="请输入合同"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="负责人" prop="ownerUserId">
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.ownerUserId"
|
v-model="queryParams.ownerUserId"
|
||||||
placeholder="请输入负责人"
|
placeholder="请输入负责人"
|
||||||
@ -105,7 +96,7 @@
|
|||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!--<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.remark"
|
v-model="queryParams.remark"
|
||||||
placeholder="请输入备注"
|
placeholder="请输入备注"
|
||||||
@ -152,8 +143,26 @@
|
|||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
<el-table-column label="ID" align="center" prop="id" />
|
<!--<el-table-column label="ID" align="center" prop="id" />-->
|
||||||
<el-table-column label="期数" align="center" prop="indexNo" />
|
<el-table-column label="客户名称" align="center" prop="customerId" width="150px" />
|
||||||
|
<el-table-column label="合同名称" align="center" prop="contractId" width="150px" />
|
||||||
|
<el-table-column label="期数" align="center" prop="period" />
|
||||||
|
<el-table-column label="计划回款" align="center" prop="price" />
|
||||||
|
<el-table-column
|
||||||
|
label="计划回款日期"
|
||||||
|
align="center"
|
||||||
|
prop="returnTime"
|
||||||
|
:formatter="dateFormatter2"
|
||||||
|
width="180px"
|
||||||
|
/>
|
||||||
|
<el-table-column label="提前几天提醒" align="center" prop="remindDays" />
|
||||||
|
<!--<el-table-column
|
||||||
|
label="提醒日期"
|
||||||
|
align="center"
|
||||||
|
prop="remindTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="180px"
|
||||||
|
/>-->
|
||||||
<!--<el-table-column label="回款ID" align="center" prop="receivableId" />-->
|
<!--<el-table-column label="回款ID" align="center" prop="receivableId" />-->
|
||||||
<el-table-column label="完成状态" align="center" prop="status">
|
<el-table-column label="完成状态" align="center" prop="status">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@ -166,26 +175,12 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<!--<el-table-column label="工作流编号" align="center" prop="processInstanceId" />-->
|
<!--<el-table-column label="工作流编号" align="center" prop="processInstanceId" />-->
|
||||||
<el-table-column label="回款金额" align="center" prop="price" />
|
<el-table-column prop="ownerUserId" label="负责人" width="120">
|
||||||
<el-table-column
|
<template #default="scope">
|
||||||
label="回款日期"
|
{{ userList.find((user) => user.id === scope.row.ownerUserId)?.nickname }}
|
||||||
align="center"
|
</template>
|
||||||
prop="returnTime"
|
</el-table-column>
|
||||||
:formatter="dateFormatter2"
|
<el-table-column label="显示顺序" align="center" prop="sort" />
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column label="提前几天提醒" align="center" prop="remindDays" />
|
|
||||||
<el-table-column
|
|
||||||
label="提醒日期"
|
|
||||||
align="center"
|
|
||||||
prop="remindTime"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column label="客户ID" align="center" prop="customerId" />
|
|
||||||
<el-table-column label="合同ID" align="center" prop="contractId" />
|
|
||||||
<el-table-column label="负责人" align="center" prop="ownerUserId" />
|
|
||||||
<!--<el-table-column label="显示顺序" align="center" prop="sort" />-->
|
|
||||||
<el-table-column label="备注" align="center" prop="remark" />
|
<el-table-column label="备注" align="center" prop="remark" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="创建时间"
|
label="创建时间"
|
||||||
@ -234,6 +229,7 @@ import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
|
|||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import * as ReceivablePlanApi from '@/api/crm/receivablePlan'
|
import * as ReceivablePlanApi from '@/api/crm/receivablePlan'
|
||||||
import ReceivablePlanForm from './ReceivablePlanForm.vue'
|
import ReceivablePlanForm from './ReceivablePlanForm.vue'
|
||||||
|
import * as UserApi from '@/api/system/user'
|
||||||
|
|
||||||
defineOptions({ name: 'ReceivablePlan' })
|
defineOptions({ name: 'ReceivablePlan' })
|
||||||
|
|
||||||
@ -243,10 +239,11 @@ const { t } = useI18n() // 国际化
|
|||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const list = ref([]) // 列表的数据
|
const list = ref([]) // 列表的数据
|
||||||
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
indexNo: null,
|
period: null,
|
||||||
status: null,
|
status: null,
|
||||||
checkStatus: null,
|
checkStatus: null,
|
||||||
returnTime: [],
|
returnTime: [],
|
||||||
@ -320,7 +317,9 @@ const handleExport = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
getList()
|
await getList()
|
||||||
|
// 获取用户列表
|
||||||
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
196
src/views/mall/promotion/rewardActivity/RewardForm.vue
Normal file
196
src/views/mall/promotion/rewardActivity/RewardForm.vue
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="80px"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<el-form-item label="活动名称" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入活动名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="活动时间" prop="startAndEndTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formData.startAndEndTime"
|
||||||
|
type="datetimerange"
|
||||||
|
range-separator="-"
|
||||||
|
:start-placeholder="t('common.startTimeText')"
|
||||||
|
:end-placeholder="t('common.endTimeText')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="条件类型" prop="conditionType">
|
||||||
|
<el-radio-group v-model="formData.conditionType">
|
||||||
|
<el-radio
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_CONDITION_TYPE)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="parseInt(dict.value)"
|
||||||
|
>{{ dict.label }}</el-radio
|
||||||
|
>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="优惠设置">
|
||||||
|
<!-- TODO 待实现!这个实现下哈 -->
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="活动商品" prop="productScope">
|
||||||
|
<el-radio-group v-model="formData.productScope">
|
||||||
|
<el-radio
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="parseInt(dict.value)"
|
||||||
|
>{{ dict.label }}</el-radio
|
||||||
|
>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- TODO:活动商品的开发,可以参考优惠劵的,已经搞好啦; -->
|
||||||
|
<el-form-item
|
||||||
|
v-if="formData.productScope === PromotionProductScopeEnum.SPU.scope"
|
||||||
|
prop="productSpuIds"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
v-model="formData.productSpuIds"
|
||||||
|
placeholder="请选择活动商品"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
style="width: 400px"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in productSpus" :key="item.id" :label="item.name" :value="item.id">
|
||||||
|
<span style="float: left">{{ item.name }}</span>
|
||||||
|
<span style="float: right; font-size: 13px; color: #8492a6"
|
||||||
|
>¥{{ (item.price / 100.0).toFixed(2) }}</span
|
||||||
|
>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input v-model="formData.remark" placeholder="请输入备注" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { getSpuSimpleList } from '@/api/mall/product/spu'
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
import * as ProductBrandApi from '@/api/mall/product/brand'
|
||||||
|
import {
|
||||||
|
PromotionConditionTypeEnum,
|
||||||
|
PromotionProductScopeEnum,
|
||||||
|
PromotionActivityStatusEnum
|
||||||
|
} from '@/utils/constants'
|
||||||
|
// 商品数据
|
||||||
|
const productSpus = ref<any[]>([])
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getSpuSimpleList().then((response) => {
|
||||||
|
productSpus.value = response
|
||||||
|
})
|
||||||
|
})
|
||||||
|
defineOptions({ name: 'ProductBrandForm' })
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
const formData = ref({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
startAndEndTime: undefined,
|
||||||
|
startTime: undefined,
|
||||||
|
endTime: undefined,
|
||||||
|
conditionType: PromotionConditionTypeEnum.PRICE.type,
|
||||||
|
remark: undefined,
|
||||||
|
productScope: PromotionProductScopeEnum.ALL.scope,
|
||||||
|
productSpuIds: undefined,
|
||||||
|
rules: undefined
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
name: [{ required: true, message: '活动名称不能为空', trigger: 'blur' }],
|
||||||
|
startAndEndTime: [{ required: true, message: '活动时间不能为空', trigger: 'blur' }],
|
||||||
|
conditionType: [{ required: true, message: '条件类型不能为空', trigger: 'change' }],
|
||||||
|
productScope: [{ required: true, message: '商品范围不能为空', trigger: 'blur' }],
|
||||||
|
productSpuIds: [{ required: true, message: '商品范围不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
// formData.value = await ProductBrandApi.getBrand(id)
|
||||||
|
formData.value = {
|
||||||
|
conditionType: 10,
|
||||||
|
description: '',
|
||||||
|
id: undefined,
|
||||||
|
name: '测试活动',
|
||||||
|
picUrl: '',
|
||||||
|
productScope: 2,
|
||||||
|
productSpuIds: [634],
|
||||||
|
remark: '测试备注',
|
||||||
|
startAndEndTime: [new Date(), new Date('2023-12-31')],
|
||||||
|
status: 0
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单
|
||||||
|
if (!formRef) return
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return
|
||||||
|
console.log(formData.value)
|
||||||
|
message.success('已在控制台打印数据')
|
||||||
|
return
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = formData.value as ProductBrandApi.BrandVO
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await ProductBrandApi.createBrand(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await ProductBrandApi.updateBrand(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
picUrl: '',
|
||||||
|
status: CommonStatusEnum.ENABLE,
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
</script>
|
213
src/views/mall/promotion/rewardActivity/index.vue
Normal file
213
src/views/mall/promotion/rewardActivity/index.vue
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_ACTIVITY_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="['product:brand:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" row-key="id" default-expand-all>
|
||||||
|
<el-table-column label="活动名称" prop="name" />
|
||||||
|
<el-table-column
|
||||||
|
label="活动开始时间"
|
||||||
|
align="center"
|
||||||
|
prop="sort[0]"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="活动结束时间"
|
||||||
|
align="center"
|
||||||
|
prop="sort[1]"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column label="状态" align="center" prop="status">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.PROMOTION_ACTIVITY_STATUS" :value="scope.row.status" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="创建时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
width="180"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column label="操作" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['product:brand:update']"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['product:brand: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>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<RewardForm ref="formRef" @success="getList" />
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import * as ProductBrandApi from '@/api/mall/product/brand'
|
||||||
|
import RewardForm from './RewardForm.vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'PromotionRewardActivity' })
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const list = ref<any[]>([]) // 列表的数据
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
name: undefined,
|
||||||
|
status: undefined,
|
||||||
|
createTime: []
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// const data = await ProductBrandApi.getBrandParam(queryParams)
|
||||||
|
const data = {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
createTime: 1693463998000,
|
||||||
|
description: '',
|
||||||
|
id: 3,
|
||||||
|
name: '索尼',
|
||||||
|
picUrl:
|
||||||
|
'http://127.0.0.1:48080/admin-api/infra/file/4/get/f5b7a536306cd1180a42a2211a8212dc23de6b949d30c30d036caa063042f928.png',
|
||||||
|
sort: [+new Date(), +new Date('2023-12-31')],
|
||||||
|
status: 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
total: 1
|
||||||
|
}
|
||||||
|
list.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
console.log(queryParams)
|
||||||
|
message.success('已打印搜索参数')
|
||||||
|
return
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
message.success('重置查询表单获取数据')
|
||||||
|
return
|
||||||
|
queryFormRef.value.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加/修改操作 */
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
message.success('您以确认删除')
|
||||||
|
return
|
||||||
|
// 发起删除
|
||||||
|
await ProductBrandApi.deleteBrand(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
@ -31,7 +31,7 @@
|
|||||||
:props="defaultProps2"
|
:props="defaultProps2"
|
||||||
class="w-1/1"
|
class="w-1/1"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请选择商品分类"
|
placeholder="请选择地区"
|
||||||
filterable
|
filterable
|
||||||
collapse-tags
|
collapse-tags
|
||||||
/>
|
/>
|
||||||
|
@ -34,7 +34,7 @@ defineOptions({ name: 'SystemLoginLogDetail' })
|
|||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const detailLoading = ref(false) // 表单的加载中
|
const detailLoading = ref(false) // 表单的加载中
|
||||||
const detailData = ref() // 详情数据
|
const detailData = ref({}) // 详情数据
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (data: LoginLogApi.LoginLogVO) => {
|
const open = async (data: LoginLogApi.LoginLogVO) => {
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<template #label>
|
<template #label>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
|
message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
|
||||||
titel="路由地址"
|
title="路由地址"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<el-input v-model="formData.path" clearable placeholder="请输入路由地址" />
|
<el-input v-model="formData.path" clearable placeholder="请输入路由地址" />
|
||||||
@ -53,7 +53,7 @@
|
|||||||
<template #label>
|
<template #label>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
|
message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
|
||||||
titel="权限标识"
|
title="权限标识"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<el-input v-model="formData.permission" clearable placeholder="请输入权限标识" />
|
<el-input v-model="formData.permission" clearable placeholder="请输入权限标识" />
|
||||||
@ -74,7 +74,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="formData.type !== 3" label="显示状态" prop="visible">
|
<el-form-item v-if="formData.type !== 3" label="显示状态" prop="visible">
|
||||||
<template #label>
|
<template #label>
|
||||||
<Tooltip message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" titel="显示状态" />
|
<Tooltip message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" title="显示状态" />
|
||||||
</template>
|
</template>
|
||||||
<el-radio-group v-model="formData.visible">
|
<el-radio-group v-model="formData.visible">
|
||||||
<el-radio key="true" :label="true" border>显示</el-radio>
|
<el-radio key="true" :label="true" border>显示</el-radio>
|
||||||
@ -85,7 +85,7 @@
|
|||||||
<template #label>
|
<template #label>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单"
|
message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单"
|
||||||
titel="总是显示"
|
title="总是显示"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<el-radio-group v-model="formData.alwaysShow">
|
<el-radio-group v-model="formData.alwaysShow">
|
||||||
@ -97,7 +97,7 @@
|
|||||||
<template #label>
|
<template #label>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段"
|
message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段"
|
||||||
titel="缓存状态"
|
title="缓存状态"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<el-radio-group v-model="formData.keepAlive">
|
<el-radio-group v-model="formData.keepAlive">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert title="功能权限" url="https://doc.iocoder.cn/resource-permission" />
|
<doc-alert title="功能权限" url="https://doc.iocoder.cn/resource-permission" />
|
||||||
<doc-alert title="菜单路由" url="https://doc.iocoder.cn/vue2/route/" />
|
<doc-alert title="菜单路由" url="https://doc.iocoder.cn/vue3/route/" />
|
||||||
|
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
|
154
src/views/system/social/client/SocialClientForm.vue
Normal file
154
src/views/system/social/client/SocialClientForm.vue
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
v-loading="formLoading"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-form-item label="应用名" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入应用名" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="社交平台" prop="socialType">
|
||||||
|
<el-radio-group v-model="formData.socialType">
|
||||||
|
<el-radio
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户类型" prop="userType">
|
||||||
|
<el-radio-group v-model="formData.userType">
|
||||||
|
<el-radio
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="客户端编号" prop="clientId">
|
||||||
|
<el-input v-model="formData.clientId" placeholder="请输入客户端编号,对应各平台的appKey" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="客户端密钥" prop="clientSecret">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.clientSecret"
|
||||||
|
placeholder="请输入客户端密钥,对应各平台的appSecret"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="agentId" prop="agentId" v-if="formData!.socialType === 30">
|
||||||
|
<el-input v-model="formData.agentId" placeholder="授权方的网页应用 ID,有则填" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-radio-group v-model="formData.status">
|
||||||
|
<el-radio
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import * as SocialClientApi from '@/api/system/social/client'
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
const formData = ref({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
socialType: undefined,
|
||||||
|
userType: undefined,
|
||||||
|
clientId: undefined,
|
||||||
|
clientSecret: undefined,
|
||||||
|
agentId: undefined,
|
||||||
|
status: 0
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
name: [{ required: true, message: '应用名不能为空', trigger: 'blur' }],
|
||||||
|
socialType: [{ required: true, message: '社交平台不能为空', trigger: 'blur' }],
|
||||||
|
userType: [{ required: true, message: '用户类型不能为空', trigger: 'blur' }],
|
||||||
|
clientId: [{ required: true, message: '客户端编号不能为空', trigger: 'blur' }],
|
||||||
|
clientSecret: [{ required: true, message: '客户端密钥不能为空', trigger: 'blur' }],
|
||||||
|
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
formData.value = await SocialClientApi.getSocialClient(id)
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单
|
||||||
|
if (!formRef) return
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = formData.value as unknown as SocialClientApi.SocialClientVO
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await SocialClientApi.createSocialClient(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await SocialClientApi.updateSocialClient(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
socialType: undefined,
|
||||||
|
userType: undefined,
|
||||||
|
clientId: undefined,
|
||||||
|
clientSecret: undefined,
|
||||||
|
agentId: undefined,
|
||||||
|
status: 0
|
||||||
|
}
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
</script>
|
227
src/views/system/social/client/index.vue
Normal file
227
src/views/system/social/client/index.vue
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
<template>
|
||||||
|
<doc-alert title="三方登录" url="https://doc.iocoder.cn/social-user/" />
|
||||||
|
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
:model="queryParams"
|
||||||
|
class="-mb-15px"
|
||||||
|
label-width="130px"
|
||||||
|
>
|
||||||
|
<el-form-item label="应用名" prop="name">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.name"
|
||||||
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入应用名"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="社交平台" prop="socialType">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.socialType"
|
||||||
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择社交平台"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户类型" prop="userType">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.userType"
|
||||||
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择用户类型"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="客户端编号" prop="clientId">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.clientId"
|
||||||
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入客户端编号"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-select v-model="queryParams.status" class="!w-240px" clearable placeholder="请选择状态">
|
||||||
|
<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 class="mr-5px" icon="ep:search" />
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:refresh" />
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['system:social-client:create']"
|
||||||
|
plain
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('create')"
|
||||||
|
>
|
||||||
|
<Icon class="mr-5px" icon="ep:plus" />
|
||||||
|
新增
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||||
|
<el-table-column align="center" label="编号" prop="id" />
|
||||||
|
<el-table-column align="center" label="应用名" prop="name" />
|
||||||
|
<el-table-column align="center" label="社交平台" prop="socialType">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.SYSTEM_SOCIAL_TYPE" :value="scope.row.socialType" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="用户类型" prop="userType">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="客户端编号" prop="clientId" width="180px" />
|
||||||
|
<el-table-column align="center" label="状态" prop="status">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
align="center"
|
||||||
|
label="创建时间"
|
||||||
|
prop="createTime"
|
||||||
|
width="180px"
|
||||||
|
/>
|
||||||
|
<el-table-column align="center" label="操作">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['system:social-client:update']"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['system:social-client:delete']"
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
:total="total"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<SocialClientForm ref="formRef" @success="getList" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import * as SocialClientApi from '@/api/system/social/client'
|
||||||
|
import SocialClientForm from './SocialClientForm.vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'SocialClient' })
|
||||||
|
|
||||||
|
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,
|
||||||
|
socialType: null,
|
||||||
|
userType: null,
|
||||||
|
clientId: null,
|
||||||
|
status: null
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await SocialClientApi.getSocialClientPage(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()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await SocialClientApi.deleteSocialClient(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
60
src/views/system/social/user/SocialUserDetail.vue
Normal file
60
src/views/system/social/user/SocialUserDetail.vue
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" title="详情" width="800">
|
||||||
|
<el-descriptions :column="1" border>
|
||||||
|
<el-descriptions-item label="社交平台" min-width="160">
|
||||||
|
<dict-tag :type="DICT_TYPE.SYSTEM_SOCIAL_TYPE" :value="detailData.type" />
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="用户昵称" min-width="120">
|
||||||
|
{{ detailData.nickname }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions label="用户头像" min-width="120">
|
||||||
|
<el-image :src="detailData.avatar" class="h-30px w-30px" />
|
||||||
|
</el-descriptions>
|
||||||
|
<el-descriptions-item label="社交 token" min-width="120">
|
||||||
|
{{ detailData.token }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="原始 Token 数据" min-width="120">
|
||||||
|
<el-input
|
||||||
|
v-model="detailData.rawTokenInfo"
|
||||||
|
:autosize="{ maxRows: 20 }"
|
||||||
|
:readonly="true"
|
||||||
|
type="textarea"
|
||||||
|
/>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="原始 User 数据" min-width="120">
|
||||||
|
<el-input
|
||||||
|
v-model="detailData.rawUserInfo"
|
||||||
|
:autosize="{ maxRows: 20 }"
|
||||||
|
:readonly="true"
|
||||||
|
type="textarea"
|
||||||
|
/>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="最后一次的认证 code" min-width="120">
|
||||||
|
{{ detailData.code }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="最后一次的认证 state" min-width="120">
|
||||||
|
{{ detailData.state }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import * as SocialUserApi from '@/api/system/social/user'
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const detailLoading = ref(false) // 表单的加载中
|
||||||
|
const detailData = ref({}) // 详情数据
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (id: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
// 设置数据
|
||||||
|
try {
|
||||||
|
detailData.value = await SocialUserApi.getSocialUser(id)
|
||||||
|
} finally {
|
||||||
|
detailLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
</script>
|
190
src/views/system/social/user/index.vue
Normal file
190
src/views/system/social/user/index.vue
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
<template>
|
||||||
|
<doc-alert title="三方登录" url="https://doc.iocoder.cn/social-user/" />
|
||||||
|
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
:model="queryParams"
|
||||||
|
class="-mb-15px"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-form-item label="社交平台" prop="type">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.type"
|
||||||
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择社交平台"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户昵称" prop="nickname">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.nickname"
|
||||||
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入用户昵称"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="社交 openid" prop="openid">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.openid"
|
||||||
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入社交 openid"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="创建时间" prop="createTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.createTime"
|
||||||
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||||
|
class="!w-240px"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="handleQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:search" />
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:refresh" />
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||||
|
<el-table-column align="center" label="社交平台" prop="type">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.SYSTEM_SOCIAL_TYPE" :value="scope.row.type" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="社交 openid" prop="openid" />
|
||||||
|
<el-table-column align="center" label="用户昵称" prop="nickname" />
|
||||||
|
<el-table-column align="center" label="用户头像" prop="avatar">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-image :src="row.avatar" class="h-30px w-30px" @click="imagePreview(row.avatar)" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
align="center"
|
||||||
|
label="创建时间"
|
||||||
|
prop="createTime"
|
||||||
|
width="180px"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
align="center"
|
||||||
|
label="更新时间"
|
||||||
|
prop="updateTime"
|
||||||
|
width="180px"
|
||||||
|
/>
|
||||||
|
<el-table-column align="center" fixed="right" label="操作">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['system:social-user:query']"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openDetail(scope.row.id)"
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
:total="total"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:详情 -->
|
||||||
|
<SocialUserDetail ref="detailRef" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import * as SocialUserApi from '@/api/system/social/user'
|
||||||
|
import SocialUserDetail from './SocialUserDetail.vue'
|
||||||
|
import { createImageViewer } from '@/components/ImageViewer'
|
||||||
|
|
||||||
|
defineOptions({ name: 'SocialUser' })
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const list = ref([]) // 列表的数据
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
type: undefined,
|
||||||
|
openid: undefined,
|
||||||
|
nickname: undefined,
|
||||||
|
createTime: []
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await SocialUserApi.getSocialUserPage(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 imagePreview = (imgUrl: string) => {
|
||||||
|
createImageViewer({
|
||||||
|
urlList: [imgUrl]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 详情操作 */
|
||||||
|
const detailRef = ref()
|
||||||
|
const openDetail = (id: number) => {
|
||||||
|
detailRef.value.open(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
@ -27,7 +27,6 @@
|
|||||||
"@intlify/unplugin-vue-i18n/types",
|
"@intlify/unplugin-vue-i18n/types",
|
||||||
"vite/client",
|
"vite/client",
|
||||||
"element-plus/global",
|
"element-plus/global",
|
||||||
"@types/intro.js",
|
|
||||||
"@types/qrcode",
|
"@types/qrcode",
|
||||||
"vite-plugin-svg-icons/client"
|
"vite-plugin-svg-icons/client"
|
||||||
],
|
],
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { defineConfig, toEscapedSelector as e, presetUno } from 'unocss'
|
import { defineConfig, toEscapedSelector as e, presetUno } from 'unocss'
|
||||||
import transformerVariantGroup from '@unocss/transformer-variant-group'
|
// import transformerVariantGroup from '@unocss/transformer-variant-group'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// ...UnoCSS options
|
// ...UnoCSS options
|
||||||
@ -101,5 +101,8 @@ ${selector}:after {
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
presets: [presetUno({ dark: 'class', attributify: false })],
|
presets: [presetUno({ dark: 'class', attributify: false })],
|
||||||
transformers: [transformerVariantGroup()]
|
// transformers: [transformerVariantGroup()],
|
||||||
|
shortcuts: {
|
||||||
|
'wh-full': 'w-full h-full'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user