提交 25ad89c0 authored 作者: 王鹏飞's avatar 王鹏飞

feat: 新增直播带货

上级 364536b3
......@@ -295,6 +295,13 @@
"WritableComputedRef": true,
"injectLocal": true,
"provideLocal": true,
"useClipboardItems": true
"useClipboardItems": true,
"DirectiveBinding": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"onWatcherCleanup": true,
"useId": true,
"useModel": true,
"useTemplateRef": true
}
}
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'./.eslintrc-auto-import.json'
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'off'
}
}
......@@ -3,6 +3,7 @@
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const $: typeof import('vue/macros')['$']
......@@ -72,6 +73,7 @@ declare global {
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const provide: typeof import('vue')['provide']
const provideLocal: typeof import('@vueuse/core')['provideLocal']
......@@ -180,6 +182,7 @@ declare global {
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGamepad: typeof import('@vueuse/core')['useGamepad']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useId: typeof import('vue')['useId']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useImage: typeof import('@vueuse/core')['useImage']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
......@@ -196,6 +199,7 @@ declare global {
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
const useMemoize: typeof import('@vueuse/core')['useMemoize']
const useMemory: typeof import('@vueuse/core')['useMemory']
const useModel: typeof import('vue')['useModel']
const useMounted: typeof import('@vueuse/core')['useMounted']
const useMouse: typeof import('@vueuse/core')['useMouse']
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
......@@ -243,6 +247,7 @@ declare global {
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
const useSupported: typeof import('@vueuse/core')['useSupported']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
......@@ -294,6 +299,6 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
import pluginVue from 'eslint-plugin-vue'
import vueTsEslintConfig from '@vue/eslint-config-typescript'
import AutoImportJson from './.eslintrc-auto-import.json' with { type: 'json' }
export default [
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
},
...pluginVue.configs['flat/essential'],
...vueTsEslintConfig(),
{
languageOptions: {
...AutoImportJson,
},
rules: {
'vue/block-lang': 'off',
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
},
},
]
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -10,7 +10,7 @@
"build:pre": "vue-tsc --noEmit && vite build --mode pre",
"preview": "vite preview --port 4173",
"typecheck": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"lint": "eslint . --fix",
"deploy": "node ./deploy.js",
"cert": "node ./cert.js"
},
......@@ -22,44 +22,43 @@
"@tinymce/tinymce-vue": "^5.0.1",
"@vue-flow/controls": "^1.1.2",
"@vue-flow/core": "^1.39.0",
"@vueuse/components": "^10.11.0",
"@vueuse/core": "^10.9.0",
"@vueuse/components": "^11.2.0",
"@vueuse/core": "^11.2.0",
"axios": "^1.6.8",
"blueimp-md5": "^2.19.0",
"dayjs": "^1.11.10",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.6.3",
"element-plus": "^2.8.7",
"file-saver": "^2.0.5",
"html-to-image": "^1.11.11",
"jspdf": "^2.5.1",
"lodash-es": "^4.17.21",
"nanoid": "^5.0.7",
"pinia": "^2.1.7",
"pinia": "^2.2.6",
"scroll-into-view-if-needed": "^3.1.0",
"vue": "^3.4.26",
"vue": "^3.5.12",
"vue-echarts": "^6.6.9",
"vue-router": "^4.3.2",
"vue-router": "^4.4.5",
"xss": "^1.0.15"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@tsconfig/node20": "^20.1.4",
"@types/blueimp-md5": "^2.18.2",
"@types/node": "^20.3.1",
"@vitejs/plugin-vue": "^5.0.4",
"@vue-macros/reactivity-transform": "^0.4.4",
"@vue/eslint-config-typescript": "^13.0.0",
"@types/node": "^20.17.6",
"@vitejs/plugin-vue": "^5.1.4",
"@vue-macros/reactivity-transform": "^1.1.3",
"@vue/eslint-config-typescript": "^14.1.3",
"@vue/tsconfig": "^0.5.1",
"ali-oss": "^6.18.1",
"chalk": "^5.2.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.25.0",
"sass": "^1.75.0",
"typescript": "~5.4.5",
"unplugin-auto-import": "^0.17.5",
"vite": "^5.2.10",
"ali-oss": "^6.21.0",
"chalk": "^5.3.0",
"eslint": "^9.14.0",
"eslint-plugin-vue": "^9.30.0",
"sass": "^1.79.6",
"typescript": "~5.6.3",
"unplugin-auto-import": "^0.18.3",
"vite": "^5.4.10",
"vite-plugin-mkcert": "^1.17.6",
"vue-tsc": "^1.8.27"
"vue-tsc": "^2.1.10"
}
}
......@@ -31,7 +31,7 @@ export function uploadFile(data: Record<string, any>) {
return httpRequest
.post(data.host || 'https://webapp-pub.ezijing.com', data, {
withCredentials: false,
headers: { 'Content-Type': 'multipart/form-data' }
headers: { 'Content-Type': 'multipart/form-data' },
})
.then(() => data)
}
......@@ -86,7 +86,13 @@ export function updateMaterial(data: { name: string; content: string; status: st
}
// 资料列表
export function getMaterialList(params?: { name: string; type: string; id: string; status: string; updated_operator: string }) {
export function getMaterialList(params?: {
name: string
type: string
id: string
status: string
updated_operator: string
}) {
return httpRequest.get('/api/lab/v1/experiment/marketing-material/list', { params })
}
......@@ -96,7 +102,12 @@ export function deleteMaterial(data: { id: string }) {
}
// 用户属性搜索
export function searchMetaMemberAttrs(params?: { search: string; member_meta_id?: string; page?: number; per_page?: number }) {
export function searchMetaMemberAttrs(params?: {
search: string
member_meta_id?: string
page?: number
per_page?: number
}) {
return httpRequest.get('/api/lab/v1/experiment/meta-member/search-attributes', { params })
}
......@@ -130,3 +141,13 @@ export function getGroupMembers(params: { group_id: string; name?: string; id?:
export function getLabelMembers(params: { tag_id: string }) {
return httpRequest.get('/api/lab/v1/experiment/tag/bda-statistics-users', { params })
}
// 获取实验直播商品品类的树形列表
export function getCategoryList(params?: { name?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity-type/trees', { params })
}
// 获取实验直播商品列表
export function getProductList(params?: { name?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity/list', { params })
}
<script setup lang="ts">
import { getCategoryList } from '@/api/base'
const options = ref([])
async function fetchList() {
const res = await getCategoryList()
options.value = res.data.trees
}
watchEffect(() => {
fetchList()
})
</script>
<template>
<el-cascader
clearable
:options="options"
placeholder="请选择"
style="width: 100%"
:props="{ label: 'name', value: 'id', emitPath: false, checkStrictly: true }" />
</template>
<script setup lang="ts">
import { getProductList } from '@/api/base'
const options = ref([])
async function fetchList() {
const res = await getProductList()
options.value = res.data.list
}
watchEffect(() => {
fetchList()
})
</script>
<template>
<el-select-v2
:options="options"
placeholder="请选择"
style="width: 100%"
value-key="id"
:props="{ label: 'title', value: 'id' }" />
</template>
......@@ -53,7 +53,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
}
} else {
console.log('error submit!')
return false
}
})
}
......@@ -84,7 +83,7 @@ const typeName = computed(() => {
:title="props.data ? (props.data?.isView ? `查看${typeName}资料` : `编辑${typeName}资料`) : `新建${typeName}资料`"
:close-on-click-modal="false"
width="800px"
@update:modelValue="value => $emit('update:modelValue', value)">
@closed="$emit('update:modelValue', false)">
<el-form
:disabled="props.data?.isView"
ref="ruleFormRef"
......@@ -97,7 +96,13 @@ const typeName = computed(() => {
<el-input placeholder="请输入" v-model="form.name" style="width: 100%"></el-input>
</el-form-item>
<el-form-item label="资料内容" prop="content">
<el-input v-if="type === '1'" placeholder="请输入" type="textarea" :autosize="{ minRows: 10 }" v-model="form.content" style="width: 100%"></el-input>
<el-input
v-if="type === '1'"
placeholder="请输入"
type="textarea"
:autosize="{ minRows: 10 }"
v-model="form.content"
style="width: 100%"></el-input>
<AppUpload v-if="type === '2' || type === '6' || type === '7' || type === '8'" v-model="form.content" />
<div class="audio" v-if="type === '3'">
<AppUpload v-model="form.content" accept=".mp3">
......
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="24" height="24">
<defs></defs>
<g>
<path
d="M21.333 5.333c0.736 0 1.333 0.597 1.333 1.333v0 5.6l6.951-4.867c0.107-0.076 0.239-0.121 0.383-0.121 0.368 0 0.667 0.298 0.667 0.667 0 0 0 0.001 0 0.001v-0 16.107c0 0 0 0 0 0.001 0 0.368-0.298 0.667-0.667 0.667-0.143 0-0.276-0.045-0.385-0.122l0.002 0.001-6.951-4.867v5.6c0 0.736-0.597 1.333-1.333 1.333v0h-18.667c-0.736 0-1.333-0.597-1.333-1.333v0-18.667c0-0.736 0.597-1.333 1.333-1.333v0h18.667zM9.867 11.772c-0 0-0 0-0 0-0.257 0-0.471 0.181-0.522 0.423l-0.001 0.003-0.011 0.105v7.389c0 0 0 0 0 0 0 0.295 0.239 0.533 0.533 0.533 0.068 0 0.133-0.013 0.193-0.036l-0.004 0.001 0.097-0.048 5.807-3.696c0.149-0.096 0.246-0.261 0.246-0.449 0-0.15-0.062-0.286-0.162-0.383l-0-0-0.084-0.067-5.807-3.696c-0.080-0.050-0.177-0.080-0.281-0.080-0.002 0-0.004 0-0.006 0h0z"></path>
</g>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="21" height="21">
<defs></defs>
<g>
<path
d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16zM16 17H5V7h11l3.55 5L16 17z"></path>
</g>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="21" height="21">
<defs></defs>
<g>
<circle cx="34.5" cy="13.5" r="6.5" fill="none" stroke="#333" stroke-width="4"></circle>
<circle cx="34.5" cy="34.5" r="6.5" fill="none" stroke="#333" stroke-width="4"></circle>
<circle cx="13.5" cy="13.5" r="6.5" fill="none" stroke="#333" stroke-width="4"></circle>
<circle cx="13.5" cy="34.5" r="6.5" fill="none" stroke="#333" stroke-width="4"></circle>
</g>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="21" height="21">
<defs></defs>
<g>
<path
d="M20 6h-2.18c.11-.31.18-.65.18-1a2.996 2.996 0 0 0-5.5-1.65l-.5.67-.5-.68A3.01 3.01 0 0 0 9 2C7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 11 8.76l1-1.36 1 1.36L15.38 12 17 10.83 14.92 8H20v6z"></path>
</g>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="20" height="20">
<defs></defs>
<g>
<path
d="M21.184 2.528l9.504 0.288c0.992 0.032 1.408 0.48 1.312 1.504-0.16 1.376-0.288 2.752-0.448 4.128-0.288 2.528-0.64 5.024-1.12 7.52-0.576 2.944-1.248 5.856-2.368 8.64-0.416 1.088-0.928 2.112-1.6 3.072-0.992 1.408-2.272 2.112-4.032 2.048-3.744-0.096-7.488-0.096-11.2-0.16-1.568 0-3.104-0.064-4.672-0.096-1.888-0.064-3.36-0.96-4.512-2.4-0.992-1.248-1.632-2.656-2.016-4.224-0.192-0.768 0.32-1.408 1.12-1.376 2.72 0.032 5.408 0.128 8.128 0.192 2.816 0.064 5.632 0.096 8.448 0.16 0.128 0 0.224 0.032 0.32 0.032 0.672-0.064 1.088 0.288 1.376 0.896 0.448 0.992 0.992 1.952 1.632 2.848 0.384 0.544 0.8 1.056 1.312 1.472 0.704 0.544 1.248 0.544 1.888-0.064 0.736-0.736 1.152-1.632 1.536-2.56 0.992-2.368 1.6-4.832 2.144-7.328 0.398-1.793 0.847-4.381 1.21-6.997l0.070-0.619c0.16-1.408 0.288-2.816 0.448-4.224 0.032-0.256-0.032-0.32-0.288-0.32-2.080-0.064-4.16-0.096-6.208-0.16-3.456-0.096-6.912-0.224-10.368-0.32-0.288 0-0.352 0.064-0.384 0.352-0.416 4.256-1.024 8.48-2.272 12.576-0.256 0.768-0.544 1.504-0.8 2.24-0.064 0.16-0.128 0.224-0.288 0.224-0.64-0.032-1.28-0.032-1.92-0.032-0.256 0-0.256-0.096-0.16-0.288 1.344-2.976 1.984-6.176 2.56-9.376 0.384-2.272 0.608-4.576 0.832-6.848 0.064-0.704 0.48-1.088 1.184-1.088l7.52 0.224c0.704 0.032 1.408 0 2.112 0v0.064zM19.68 27.488c-0.832-1.088-1.44-2.176-2.016-3.296-0.064-0.128-0.16-0.192-0.32-0.16-0.32 0-0.64-0.032-0.96-0.032l-8.128-0.192-5.056-0.096c-0.608-0.032-0.608-0.032-0.352 0.512 0.928 1.792 2.144 3.104 4.384 3.104 3.776 0 7.52 0.096 11.296 0.16h1.152zM20.448 9.952c-1.568-0.032-3.104-0.096-4.64-0.128-0.16-0.032-0.288-0.032-0.448-0.064-0.352-0.032-0.576-0.288-0.544-0.64 0-0.352 0.224-0.576 0.576-0.64h0.416c3.168 0.128 6.336 0.256 9.504 0.352 0.448 0.032 0.704 0.288 0.704 0.672-0.032 0.384-0.288 0.64-0.736 0.608-1.6-0.032-3.232-0.096-4.832-0.128v-0.032zM22.784 14.56l-7.936-0.288c-0.448 0-0.736-0.192-0.768-0.544-0.064-0.32 0.096-0.544 0.384-0.704 0.128-0.064 0.288-0.032 0.448-0.032l4 0.128c1.664 0.064 3.296 0.128 4.928 0.16 0.224 0.032 0.448 0.032 0.608 0.224 0.192 0.192 0.224 0.416 0.16 0.672-0.128 0.288-0.352 0.416-0.672 0.448-0.384 0-1.152-0.032-1.152-0.064zM18.336 19.008c-1.664-0.096-3.328-0.192-4.96-0.288-0.288 0-0.448-0.192-0.48-0.544-0.032-0.32 0.064-0.544 0.256-0.704 0.064-0.064 0.16-0.032 0.256-0.032l2.496 0.128c1.056 0.064 2.080 0.128 3.104 0.16 0.128 0.032 0.256 0.032 0.352 0.224 0.128 0.192 0.16 0.448 0.096 0.672-0.064 0.288-0.224 0.416-0.384 0.448-0.256 0-0.736-0.032-0.736-0.064z"></path>
</g>
</svg>
</template>
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="20" height="20">
<defs></defs>
<g>
<path
d="M21.333 5.333c0.736 0 1.333 0.597 1.333 1.333v0 5.6l6.951-4.867c0.107-0.076 0.239-0.121 0.383-0.121 0.368 0 0.667 0.298 0.667 0.667 0 0 0 0.001 0 0.001v-0 16.107c0 0 0 0 0 0.001 0 0.368-0.298 0.667-0.667 0.667-0.143 0-0.276-0.045-0.385-0.122l0.002 0.001-6.951-4.867v5.6c0 0.736-0.597 1.333-1.333 1.333v0h-18.667c-0.736 0-1.333-0.597-1.333-1.333v0-18.667c0-0.736 0.597-1.333 1.333-1.333v0h18.667zM20 8h-16v16h16v-16zM9.867 11.772c0 0 0.001 0 0.001 0 0.106 0 0.204 0.031 0.287 0.084l-0.002-0.001 5.807 3.696c0.149 0.096 0.246 0.261 0.246 0.449s-0.097 0.353-0.244 0.448l-0.002 0.001-5.807 3.697c-0.081 0.052-0.18 0.084-0.287 0.084-0.295 0-0.533-0.239-0.533-0.533 0-0.001 0-0.003 0-0.004v0-7.387c0-0.295 0.24-0.533 0.533-0.533zM28 11.787l-5.333 3.733v0.957l5.333 3.733v-8.424z"></path>
</g>
</svg>
</template>
......@@ -109,7 +109,7 @@ watch(
watch(
() => form.value.rule,
() => {
let type = form.value.rule === '102' ? 1 : 2
const type = form.value.rule === '102' ? 1 : 2
form.value.basis === '1' ? fetchUserAttrList(type) : fetchMetaEventList(type)
},
{ immediate: true }
......
......@@ -35,7 +35,7 @@ async function handelSubmit() {
</script>
<template>
<el-dialog title="创建营销漏斗分析" width="600" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog title="创建营销漏斗分析" width="600" @closed="$emit('update:modelValue', false)">
<el-button type="primary" @click="handleAdd">添加漏斗步数</el-button>
<div class="rule-item" v-for="(item, index) in rules" :key="index">
<el-button type="primary">{{ index + 1 }}</el-button>
......
......@@ -54,11 +54,13 @@ const refetch = function () {
class="data-form"
title="数据生成进度"
:close-on-click-modal="false"
@update:modelValue="value => $emit('update:modelValue', value)">
@closed="$emit('update:modelValue', false)">
<AppList v-bind="listOptions" ref="appList">
<template #table-x="{ row }">
<el-button type="primary" plain @click="refetch" v-permission="'v1-experiment-member-delete'">刷新</el-button>
<el-button type="primary" plain @click="handleDelete(row)" v-permission="'v1-experiment-member-delete'">删除</el-button>
<el-button type="primary" plain @click="handleDelete(row)" v-permission="'v1-experiment-member-delete'"
>删除</el-button
>
</template></AppList
>
</el-dialog>
......
......@@ -19,7 +19,7 @@ const emit = defineEmits<{
// const formSize = ref('default')
const ruleFormRef = ref<FormInstance>()
let ruleForm = $ref<any>({
const ruleForm = $ref<any>({
experiment_id: props.data?.experiment_id,
connect_id: props.data?.connect_id,
event_id: '',
......@@ -116,9 +116,16 @@ const option = computed(() => {
class="data-form"
title="自动生成用户事件数据"
:close-on-click-modal="false"
@update:modelValue="value => $emit('update:modelValue', value)">
@closed="$emit('update:modelValue', false)">
<div class="button-flex">
<el-form :disabled="userStore.role?.id === 1" label-suffix=":" ref="ruleFormRef" :model="ruleForm" label-width="auto" class="demo-ruleForm" status-icon>
<el-form
:disabled="userStore.role?.id === 1"
label-suffix=":"
ref="ruleFormRef"
:model="ruleForm"
label-width="auto"
class="demo-ruleForm"
status-icon>
<el-form :model="ruleForm" label-suffix=":">
<el-form-item label="用户事件" :rules="rules" label-width="205">
<el-select @change="eventChange" v-model="ruleForm.event_id" placeholder="请选择">
......
......@@ -231,8 +231,7 @@ async function handleSave() {
:title="props.data?.id ? '编辑连接' : '新建连接'"
:close-on-click-modal="false"
width="1050px"
@update:modelValue="value => $emit('update:modelValue', value)"
>
@closed="$emit('update:modelValue', false)">
<el-tabs v-model="stepActive" class="demo-tabs">
<!-- 第一步 -->
<el-tab-pane disabled lazy label="选择连接类型" :name="1" v-if="!props.data?.id">
......
......@@ -99,7 +99,7 @@ const sendChat = function () {
title="关注"
:close-on-click-modal="false"
:style="`width: fit-content; ${step === 4 ? 'background-color: #f1f1f1;' : 'background-color: #fff;'}`"
@update:modelValue="value => $emit('update:modelValue', value)">
@closed="$emit('update:modelValue', false)">
<div class="step1" v-if="step === 1">
<div class="connect-item__icon">
<Icon w="60" h="60" :multiColor="true" class="svg" :name="props.data?.type || '1'"></Icon>
......@@ -110,8 +110,18 @@ const sendChat = function () {
</div>
</div>
<div class="step2" v-if="step === 2">
<el-form ref="ruleFormRef" style="width: 400px" :model="ruleForm" :rules="rules" label-width="auto" class="demo-ruleForm" status-icon center>
<p style="color: #ccc; font-size: 12px; text-align: center; margin-bottom: 20px">注:填写信息保存之后,关注成功</p>
<el-form
ref="ruleFormRef"
style="width: 400px"
:model="ruleForm"
:rules="rules"
label-width="auto"
class="demo-ruleForm"
status-icon
center>
<p style="color: #ccc; font-size: 12px; text-align: center; margin-bottom: 20px">
注:填写信息保存之后,关注成功
</p>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="ruleForm.gender">
<el-radio value="1"></el-radio>
......
......@@ -23,10 +23,14 @@ function handleSubmit() {
<div class="surveyKing-tips" @click="modelValue = true">
<el-icon><Lock /></el-icon>
</div>
<el-dialog title="账号密码" width="500" v-model="modelValue" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog title="账号密码" width="500" v-model="modelValue" @closed="$emit('update:modelValue', false)">
<el-form labelWidth="140" labelSuffix=":">
<el-form-item label="账号">{{ account }} <el-button text type="primary" @click="handleCopy(account)">复制</el-button></el-form-item>
<el-form-item label="密码">{{ password }} <el-button text type="primary" @click="handleCopy(password)">复制</el-button></el-form-item>
<el-form-item label="账号"
>{{ account }} <el-button text type="primary" @click="handleCopy(account)">复制</el-button></el-form-item
>
<el-form-item label="密码"
>{{ password }} <el-button text type="primary" @click="handleCopy(password)">复制</el-button></el-form-item
>
</el-form>
<template #footer>
<el-button type="primary" @click="handleSubmit">确定</el-button>
......
......@@ -36,7 +36,7 @@ async function handleSubmit() {
</script>
<template>
<el-dialog title="字段映射" width="500" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog title="字段映射" width="500" @closed="$emit('update:modelValue', false)">
<el-form labelWidth="240" labelPosition="left">
<el-form-item v-for="item in form" :key="item.survey_field_id" :label="item.title">
<el-select v-model="item.field_id" style="width: 220px">
......
......@@ -19,7 +19,7 @@ const emit = defineEmits<{
// const formSize = ref('default')
const ruleFormRef = ref<FormInstance>()
let ruleForm = $ref<any>({
const ruleForm = $ref<any>({
experiment_id: '',
connect_id: '',
size: 1000,
......@@ -81,7 +81,7 @@ const submitForm = async (formEl: FormInstance | undefined, bl: string) => {
const rules = [{ required: true }]
let genderWoman = ref(0)
const genderWoman = ref(0)
watchEffect(() => {
if (ruleForm.gender > 100) ruleForm.gender = 100
if (ruleForm.gender < 0) ruleForm.gender = 0
......@@ -95,8 +95,7 @@ watchEffect(() => {
class="data-form"
title="自动生成用户数据"
:close-on-click-modal="false"
@update:modelValue="value => $emit('update:modelValue', value)"
>
@closed="$emit('update:modelValue', false)">
<div class="button-flex">
<el-form
:disabled="userStore.role?.id === 1"
......@@ -105,8 +104,7 @@ watchEffect(() => {
:model="ruleForm"
label-width="auto"
class="demo-ruleForm"
status-icon
>
status-icon>
<el-form-item label="请输入需要生成的数据量" :rules="rules">
<el-radio-group v-model="ruleForm.size">
<el-radio :value="1000">1000</el-radio>
......@@ -114,7 +112,9 @@ watchEffect(() => {
<el-radio :value="5000">5000</el-radio>
<el-radio :value="10000">10000</el-radio>
</el-radio-group>
<span style="color: #ccc;font-size: 12px;line-height: 100%;">注意:为了保障系统性能,您最多只能导入10000条数据</span>
<span style="color: #ccc; font-size: 12px; line-height: 100%"
>注意:为了保障系统性能,您最多只能导入10000条数据</span
>
</el-form-item>
<el-form-item label="请选择数据覆盖形式:" :rules="rules">
<el-radio-group v-model="ruleForm.cover_type">
......
......@@ -10,7 +10,7 @@ const route = useRoute()
const connectId = computed<string>(() => (route.query.id as string) || '')
let detail = ref<any>()
const detail = ref<any>()
async function fetchInfo() {
const res = await getConnectionDetails({ id: connectId.value })
......
......@@ -56,7 +56,7 @@ function handleAdd() {
</script>
<template>
<el-dialog title="添加群组用户" width="980px" append-to-body @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog title="添加群组用户" width="980px" append-to-body @closed="$emit('update:modelValue', false)">
<el-form label-suffix=":" label-width="82px">
<el-row>
<el-col :span="8">
......@@ -85,7 +85,9 @@ function handleAdd() {
<template #footer>
<el-row justify="center">
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" plain auto-insert-space @click="handleAdd" :disabled="!multipleSelection.length">添加</el-button>
<el-button type="primary" plain auto-insert-space @click="handleAdd" :disabled="!multipleSelection.length"
>添加</el-button
>
</el-row>
</template>
</el-dialog>
......
......@@ -3,7 +3,15 @@ import type { Group } from '../types'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { getNameByValue, updateStatusRuleList, dateUnitList, weekList, groupTypeList } from '@/utils/dictionary'
import { createStaticGroup, updateStaticGroup, createDynamicGroup, updateDynamicGroup, getGroupInfo, createRFMGroup, updateRFMGroup } from '../api'
import {
createStaticGroup,
updateStaticGroup,
createDynamicGroup,
updateDynamicGroup,
getGroupInfo,
createRFMGroup,
updateRFMGroup
} from '../api'
import UserRule from '@/components/rule/UserRule.vue'
import EventRule from '@/components/rule/EventRule.vue'
import LabelRule from '@/components/rule/LabelRule.vue'
......@@ -116,7 +124,16 @@ async function handleCreate() {
tag_rule: JSON.stringify([{ ...form.tag_rule, ...tagRule }]),
user_action_rule: JSON.stringify(form.user_action_rule)
},
['name', 'status', 'update_status', 'update_rule', 'user_attr_rule', 'event_attr_rule', 'tag_rule', 'user_action_rule']
[
'name',
'status',
'update_status',
'update_rule',
'user_attr_rule',
'event_attr_rule',
'tag_rule',
'user_action_rule'
]
)
await createDynamicGroup(params)
} else {
......@@ -171,7 +188,17 @@ async function handleUpdate() {
tag_rule: JSON.stringify([{ ...form.tag_rule, ...tagRule }]),
user_action_rule: JSON.stringify(form.user_action_rule)
},
['id', 'name', 'status', 'update_status', 'update_rule', 'user_attr_rule', 'event_attr_rule', 'tag_rule', 'user_action_rule']
[
'id',
'name',
'status',
'update_status',
'update_rule',
'user_attr_rule',
'event_attr_rule',
'tag_rule',
'user_action_rule'
]
)
await updateDynamicGroup(params)
} else {
......@@ -193,7 +220,7 @@ async function handleUpdate() {
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" width="980px" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog :title="title" :close-on-click-modal="false" width="980px" @closed="$emit('update:modelValue', false)">
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="100px">
<el-form-item label="群组名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
......@@ -201,14 +228,22 @@ async function handleUpdate() {
<template v-if="data.type !== '1'">
<el-form-item label="更新频率" prop="update_status">
<el-radio-group v-model="form.update_status">
<el-radio v-for="item in updateStatusRuleList" :key="item.value" :value="item.value" :disabled="item.value === '1'">
<el-radio
v-for="item in updateStatusRuleList"
:key="item.value"
:value="item.value"
:disabled="item.value === '1'">
{{ item.label }}
</el-radio>
</el-radio-group>
<div class="update-rule-wrap" v-if="form.update_status === '1'">
<span></span>
<el-select v-model="form.update_rule.type" placeholder=" " style="width: 60px">
<el-option v-for="item in dateUnitList" :key="item.value" :label="item.label" :value="item.value"></el-option>
<el-option
v-for="item in dateUnitList"
:key="item.value"
:label="item.label"
:value="item.value"></el-option>
</el-select>
<template v-if="form.update_rule.type === 1">
<span>的凌晨更新</span>
......@@ -216,7 +251,11 @@ async function handleUpdate() {
<template v-if="form.update_rule.type === 2">
<span></span>
<el-select v-model="form.update_rule.info" placeholder=" " style="width: 80px">
<el-option v-for="item in weekList" :key="item.value" :label="item.label" :value="item.value"></el-option>
<el-option
v-for="item in weekList"
:key="item.value"
:label="item.label"
:value="item.value"></el-option>
</el-select>
<span>的凌晨更新</span>
</template>
......
......@@ -47,7 +47,7 @@ function handleClearMembers() {
})
}
// 添加群组成员
let selectMembersVisible = ref(false)
const selectMembersVisible = ref(false)
function handleAdd() {
selectMembersVisible.value = true
}
......@@ -100,7 +100,7 @@ function handleRefresh() {
</script>
<template>
<el-dialog title="查看群组信息" width="1000px" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog title="查看群组信息" width="1000px" @closed="$emit('update:modelValue', false)">
<el-form label-suffix=":" label-width="82px">
<el-row>
<el-col :span="12">
......@@ -150,7 +150,14 @@ function handleRefresh() {
<dt>更新状态</dt>
<dd>
<span style="margin-right: 10px">{{ getNameByValue(detail.status.toString(), updateStatusList) }}</span>
<el-button type="primary" plain @click="handleUpdate" size="small" :disabled="['2', '4'].includes(detail.status.toString())">立即更新</el-button>
<el-button
type="primary"
plain
@click="handleUpdate"
size="small"
:disabled="['2', '4'].includes(detail.status.toString())"
>立即更新</el-button
>
</dd>
</dl>
</template>
......@@ -161,7 +168,11 @@ function handleRefresh() {
<AppList v-bind="listOptions" ref="appList">
<template #table-x="{ row }">
<el-button type="primary" plain>
<router-link target="_blank" :to="{ path: '/user/image', query: { user_id: row.id, experiment_id: row.experiment_id } }">查看</router-link>
<router-link
target="_blank"
:to="{ path: '/user/image', query: { user_id: row.id, experiment_id: row.experiment_id } }"
>查看</router-link
>
</el-button>
</template>
</AppList>
......@@ -172,7 +183,11 @@ function handleRefresh() {
<el-button plain auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
</el-row>
</template>
<BindMembers :data="data" v-model="selectMembersVisible" @update="handleRefresh" v-if="selectMembersVisible"></BindMembers>
<BindMembers
:data="data"
v-model="selectMembersVisible"
@update="handleRefresh"
v-if="selectMembersVisible"></BindMembers>
</el-dialog>
</template>
......
......@@ -93,7 +93,7 @@ function handleUpdate() {
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @closed="$emit('update:modelValue', false)">
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="100px">
<el-form-item label="标签名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
......@@ -113,14 +113,22 @@ function handleUpdate() {
</el-form-item>
<el-form-item label="更新频率" prop="update_status">
<el-radio-group v-model="form.update_status">
<el-radio v-for="item in updateStatusRuleList" :key="item.value" :value="item.value" :disabled="item.value === '1'">
<el-radio
v-for="item in updateStatusRuleList"
:key="item.value"
:value="item.value"
:disabled="item.value === '1'">
{{ item.label }}
</el-radio>
</el-radio-group>
<div class="update-rule-wrap" v-if="form.update_status === '1'">
<span></span>
<el-select v-model="form.update_rule.type" placeholder=" " style="width: 60px">
<el-option v-for="item in dateUnitList" :key="item.value" :label="item.label" :value="item.value"></el-option>
<el-option
v-for="item in dateUnitList"
:key="item.value"
:label="item.label"
:value="item.value"></el-option>
</el-select>
<template v-if="form.update_rule.type === 1">
<span>的凌晨更新</span>
......
......@@ -115,7 +115,11 @@ function handleUpdate() {
</script>
<template>
<el-dialog title="标签规则管理" :close-on-click-modal="false" width="1100px" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog
title="标签规则管理"
:close-on-click-modal="false"
width="1100px"
@closed="$emit('update:modelValue', false)">
<el-form label-suffix=":" label-width="82px">
<el-row>
<el-col :span="12">
......
......@@ -56,7 +56,7 @@ function handleUpdate() {
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @closed="$emit('update:modelValue', false)">
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="100px">
<el-form-item label="目录名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
......
import httpRequest from '@/utils/axios'
// 获取实验直播商品属性的列表
export function getAttrList(params?: { name?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity-attr/list', { params })
}
// 获取实验直播商品属性详情
export function getAttr(params: { id: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity-attr/detail', { params })
}
// 创建实验直播商品属性
export function createAttr(data: { pid: string; name: string; status: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-commodity-attr/create', data)
}
// 更新实验直播商品属性详情
export function updateAttr(data: { id: string; name: string; status: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-commodity-attr/update', data)
}
// 删除实验直播商品属性
export function deleteAttr(data: { id: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-commodity-attr/delete', data)
}
<script setup>
import { ElMessage } from 'element-plus'
import { importType, requiredType } from '@/utils/dictionary'
import { createAttr, updateAttr } from '../api'
import LiveProductCategory from '@/components/LiveProductCategory.vue'
const props = defineProps(['data', 'action'])
const emit = defineEmits(['update', 'update:modelValue'])
const actonMap = { add: '新增', update: '编辑', view: '查看' }
const isUpdate = computed(() => props.action === 'update')
const title = computed(() => actonMap[props.action] + '商品属性')
const formRef = ref()
const form = reactive({
name: '',
live_commodity_type_id: '',
is_importance: '0',
is_essential: '0',
status: '1',
})
watchEffect(() => {
if (props.data) Object.assign(form, props.data)
})
const rules = ref({
name: [{ required: true, message: '请输入' }],
live_commodity_type_id: [{ required: true, message: '请选择' }],
is_importance: [{ required: true, message: '请选择' }],
is_essential: [{ required: true, message: '请选择' }],
status: [{ required: true, message: '请选择' }],
})
// 提交
async function handleSubmit() {
await formRef.value?.validate()
isUpdate.value ? handleUpdate() : handleAdd()
}
// 新建
async function handleAdd() {
await createAttr(form)
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
// 编辑
async function handleUpdate() {
await updateAttr(form)
ElMessage({ message: '修改成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" @closed="$emit('update:modelValue', false)">
<el-row justify="center">
<el-col :sm="24" :md="16">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" :disabled="action === 'view'">
<el-form-item label="商品属性名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<el-form-item label="所属商品品类" prop="live_commodity_type_id">
<LiveProductCategory v-model="form.live_commodity_type_id" style="width: 100%" placeholder="请选择" />
<!-- <el-select v-model="form.live_commodity_type_id" style="width: 100%" placeholder="请选择"> </el-select> -->
</el-form-item>
<el-form-item label="重要性" prop="is_importance">
<el-radio-group v-model="form.is_importance" :disabled="isUpdate">
<el-radio v-for="item in importType" v-bind="item" :key="item.value"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="必要性" prop="is_essential">
<el-radio-group v-model="form.is_essential">
<el-radio v-for="item in requiredType" v-bind="item" :key="item.value"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch
v-model="form.status"
active-text="生效"
active-value="1"
inactive-text="失效"
inactive-value="0" />
</el-form-item>
</el-form>
</el-col>
</el-row>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" round auto-insert-space @click="handleSubmit" v-if="action !== 'view'"
>保存</el-button
>
</el-row>
</template>
</el-dialog>
</template>
import type { RouteRecordRaw } from 'vue-router'
import Layout from '@/components/layout/Index.vue'
const routes: RouteRecordRaw[] = [
{
path: '/live/product/attr',
component: Layout,
children: [{ path: '', component: () => import('./views/Index.vue') }]
}
]
export { routes }
<script setup>
import { ElMessageBox, ElMessage } from 'element-plus'
import { getAttrList, deleteAttr } from '../api'
import { useMapStore } from '@/stores/map'
import { getNameByValue, importType, requiredType } from '@/utils/dictionary'
import LiveProductCategory from '@/components/LiveProductCategory.vue'
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const statusList = useMapStore().getMapValuesByKey('system_status')
const appList = ref(null)
// 刷新
const handleRefresh = () => {
appList.value?.refetch()
}
const listParams = reactive({ name: '', live_commodity_type_id: '' })
// 列表配置
const listOptions = computed(() => {
return {
remote: {
httpRequest: getAttrList,
params: listParams,
beforeRequest(params, isReset) {
if (isReset) listParams.live_commodity_type_id = ''
params.live_commodity_type_id = listParams.live_commodity_type_id
return params
},
},
filters: [
{ label: '商品品类', prop: 'live_commodity_type_id', slots: 'filter-category' },
{ label: '商品属性', prop: 'name', type: 'input', placeholder: '请输入商品属性名称' },
],
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '商品属性名称', prop: 'name' },
{ label: '所属商品品类', prop: 'live_commodity_type_full_name' },
{
label: '重要性',
prop: 'is_importance',
computed({ row }) {
return getNameByValue(row.is_importance, importType)
},
},
{
label: '必要性',
prop: 'is_essential',
computed({ row }) {
return getNameByValue(row.is_essential, requiredType)
},
},
{
label: '状态',
prop: 'status',
computed({ row }) {
return getNameByValue(row.status, statusList)
},
},
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 160 },
],
}
})
const action = ref(null)
const formVisible = ref(false)
const currentRow = ref(null)
// 新建
const handleAdd = () => {
action.value = 'add'
currentRow.value = null
formVisible.value = true
}
// 编辑
const handleUpdate = (row) => {
action.value = 'update'
currentRow.value = row
formVisible.value = true
}
// 查看
const handleView = (row) => {
action.value = 'view'
currentRow.value = row
formVisible.value = true
}
// 删除
const handleRemove = async (row) => {
await ElMessageBox.confirm('此操作将永久删除该数据, 是否继续?', '提示')
await deleteAttr({ id: row.id })
ElMessage.success('删除成功')
appList.value?.refetch()
}
</script>
<template>
<AppCard title="商品属性管理">
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary" @click="handleAdd">新增商品属性</el-button>
</template>
<template #filter-category>
<LiveProductCategory v-model="listParams.live_commodity_type_id" @change="handleRefresh"></LiveProductCategory>
</template>
<template #table-x="{ row }">
<el-button text type="primary" @click="handleView(row)">查看</el-button>
<el-button text type="primary" @click="handleUpdate(row)">编辑</el-button>
<el-button text type="primary" @click="handleRemove(row)">删除</el-button>
</template>
</AppList>
</AppCard>
<!-- 新建/修改 -->
<FormDialog v-model="formVisible" :action="action" :data="currentRow" @update="handleRefresh" v-if="formVisible" />
</template>
import httpRequest from '@/utils/axios'
// 获取实验直播商品品类的树形列表
export function getCategoryList(params?: { name?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity-type/trees', { params })
}
// 获取实验直播商品品类详情
export function getCategory(params: { id: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity-type/detail', { params })
}
// 创建实验直播商品品类
export function createCategory(data: { pid: string; name: string; status: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-commodity-type/create', data)
}
// 更新实验直播商品品类详情
export function updateCategory(data: { id: string; name: string; status: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-commodity-type/update', data)
}
// 删除实验直播商品品类
export function deleteCategory(data: { id: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-commodity-type/delete', data)
}
<script setup>
import { ElMessage } from 'element-plus'
import { createCategory, updateCategory } from '../api'
const props = defineProps(['data'])
const emit = defineEmits(['update', 'update:modelValue'])
const isUpdate = computed(() => !!props.data?.id)
const title = computed(() => (isUpdate.value ? '编辑类别' : '添加类别'))
const formRef = ref()
const form = reactive({
name: '',
status: '1',
})
watchEffect(() => {
if (props.data) Object.assign(form, props.data)
})
const rules = ref({
name: [{ required: true, message: '请输入' }],
status: [{ required: true, message: '请输入' }],
})
// 提交
async function handleSubmit() {
await formRef.value?.validate()
isUpdate.value ? handleUpdate() : handleAdd()
}
// 新建
async function handleAdd() {
await createCategory(form)
ElMessage({ message: '新增成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
// 新建
async function handleUpdate() {
await updateCategory(form)
ElMessage({ message: '修改成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" @closed="$emit('update:modelValue', false)">
<el-row justify="center">
<el-col :sm="24" :md="16">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
<el-form-item label="类别名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch
v-model="form.status"
active-text="生效"
active-value="1"
inactive-text="失效"
inactive-value="0" />
</el-form-item>
</el-form>
</el-col>
</el-row>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
</el-row>
</template>
</el-dialog>
</template>
import type { RouteRecordRaw } from 'vue-router'
import Layout from '@/components/layout/Index.vue'
const routes: RouteRecordRaw[] = [
{
path: '/live/product/category',
component: Layout,
children: [{ path: '', component: () => import('./views/Index.vue') }]
}
]
export { routes }
<script setup>
import { ElMessageBox, ElMessage } from 'element-plus'
import { getCategoryList, deleteCategory } from '../api'
import { useMapStore } from '@/stores/map'
import { getNameByValue } from '@/utils/dictionary'
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const statusList = useMapStore().getMapValuesByKey('system_status')
const appList = ref(null)
// 刷新
const handleRefresh = () => {
appList.value?.refetch()
}
// 列表配置
const listOptions = {
hasPagination: false,
remote: {
httpRequest: getCategoryList,
callback(data) {
return { list: data.trees }
},
},
filters: [{ label: '商品品类名称', prop: 'name', type: 'input', placeholder: '请输入类别名称' }],
columns: [
{ label: '商品品类名称', prop: 'name', align: 'left' },
{ label: '层级', prop: 'level' },
{
label: '状态',
prop: 'status',
computed({ row }) {
return getNameByValue(row.status, statusList)
},
},
{ label: '操作', slots: 'table-x', width: 160 },
],
}
const formVisible = ref(false)
const currentRow = ref(null)
// 新建
const handleAdd = (row) => {
formVisible.value = true
currentRow.value = row || { pid: 0 }
}
// 编辑
const handleUpdate = (row) => {
formVisible.value = true
currentRow.value = row
}
const handleRemove = async (row) => {
await ElMessageBox.confirm('此操作将永久删除该数据, 是否继续?', '提示')
await deleteCategory({ id: row.id })
ElMessage.success('删除成功')
appList.value?.refetch()
}
</script>
<template>
<AppCard title="商品品类管理">
<AppList v-bind="listOptions" row-key="id" ref="appList">
<template #header-buttons>
<el-button type="primary" @click="handleAdd()">新增类别</el-button>
</template>
<template #table-x="{ row }">
<el-button text type="primary" @click="handleUpdate(row)">编辑</el-button>
<el-button text type="primary" @click="handleAdd({ pid: row.id })">新增</el-button>
<el-button text type="primary" @click="handleRemove(row)">删除</el-button>
</template>
</AppList>
</AppCard>
<!-- 新建/修改 -->
<FormDialog v-model="formVisible" :data="currentRow" @update="handleRefresh" v-if="formVisible" />
</template>
import httpRequest from '@/utils/axios'
// 获取实验直播商品的列表
export function getProductList(params?: { name?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity/list', { params })
}
// 获取实验直播商品详情
export function getProduct(params: { id: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity/detail', { params })
}
// 创建实验直播商品
export function createProduct(data: { pid: string; name: string; status: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-commodity/create', data)
}
// 更新实验直播商品详情
export function updateProduct(data: { id: string; name: string; status: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-commodity/update', data)
}
// 删除实验直播商品
export function deleteProduct(data: { id: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-commodity/delete', data)
}
// 获取实验直播商品品类的树形列表
export function getCategoryList(params?: { name?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity-type/trees', { params })
}
// 获取直播商品品类下的所有属性
export function getAttrList(params: { live_commodity_type_id: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity-attr/search', { params })
}
<script setup>
import { getAttrList } from '../api'
const form = inject('form')
const attrs = reactive({
importance_attrs: {
items: [],
total: 0,
},
unimportance_attrs: {
items: [],
total: 0,
},
})
async function fetchList() {
if (!form.live_commodity_type_id) return
const res = await getAttrList({ live_commodity_type_id: form.live_commodity_type_id })
Object.assign(attrs, res.data)
}
watchEffect(() => {
fetchList()
})
function handleChange(value, item, attr) {
form.live_commodity_attrs[attr][item.id] = {
id: item.id,
attr_name: item.name,
value: value,
}
}
</script>
<template>
<div>
<el-form-item label="商品标题" prop="title" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<el-input placeholder="至少输入8个字(16个字符)以上,30个字(60个字符)以下" size="large" v-model="form.title" />
</el-form-item>
<el-form-item label="导购短标题" prop="shopping_guide_short_title">
<el-input
placeholder="建议填写简明准确的标题内容,避免重复表达"
size="large"
v-model="form.shopping_guide_short_title" />
</el-form-item>
<el-form-item label="重要属性" prop="live_commodity_attrs.importance_attrs">
<el-form-item
v-for="item in attrs.importance_attrs.items"
:key="item.id"
:label="item.name"
style="width: 200px; margin-right: 20px">
<el-input
placeholder="请输入"
:modelValue="form.live_commodity_attrs.importance_attrs[item.id]?.value"
@input="(value) => handleChange(value, item, 'importance_attrs')"></el-input>
</el-form-item>
</el-form-item>
<el-form-item label="非重要属性" prop="live_commodity_attrs.unimportance">
<el-form-item
v-for="item in attrs.unimportance_attrs.items"
:key="item.id"
:label="item.name"
style="width: 200px; margin-right: 20px">
<el-input
placeholder="请输入"
:modelValue="form.live_commodity_attrs.unimportance_attrs[item.id]?.value"
@input="(value) => handleChange(value, item, 'unimportance_attrs')"></el-input>
</el-form-item>
</el-form-item>
</div>
</template>
<script setup>
const form = inject('form')
import { getCategoryList } from '../api'
const options = ref([])
async function fetchList() {
const res = await getCategoryList()
options.value = res.data.trees
}
watchEffect(() => {
fetchList()
})
</script>
<template>
<div>
<el-form-item label="选择商品类目" prop="live_commodity_type_id" :rules="[{ required: true, message: '请选择' }]">
<!-- <el-input placeholder="请输入关键词搜索商品分类" size="large" /> -->
<el-cascader-panel
v-model="form.live_commodity_type_id"
:options="options"
:props="{ label: 'name', value: 'id', emitPath: false }"
placeholder="请输入关键词搜索商品分类"
size="large"
filterable
clearable
style="width: 100%; margin-top: 20px" />
</el-form-item>
</div>
</template>
<script setup>
import AppUpload from '@/components/base/AppUpload.vue'
const form = inject('form')
</script>
<template>
<div>
<el-form-item label="主图" required>
<AppUpload v-model="form.picture_addreses"></AppUpload>
</el-form-item>
<el-form-item label="主图3:4">
<AppUpload v-model="form.picture_34_addreses"></AppUpload>
</el-form-item>
<el-form-item label="主图视频">
<AppUpload v-model="form.video_url"></AppUpload>
</el-form-item>
</div>
</template>
<script setup>
import { deliveryMode, deliveryTime, orderStockCount } from '@/utils/dictionary'
const form = inject('form')
</script>
<template>
<div>
<el-form-item label="发货模式" prop="info.delivery_mode" :rules="[{ required: true, message: '请选择' }]">
<el-radio-group v-model="form.info.delivery_mode">
<el-radio v-for="item in deliveryMode" :key="item.value" v-bind="item"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="商品规格"> </el-form-item>
<el-form-item label="发货时效" prop="info.delivery_time">
<el-radio-group v-model="form.info.delivery_time">
<el-radio v-for="item in deliveryTime" :key="item.value" v-bind="item"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="价格与库存" required>
<el-table :data="form.info.sku" border :header-cell-style="{ backgroundColor: '#f5f7fa' }">
<el-table-column prop="price" label="价格" align="center">
<template #default="{ row }">
<el-input v-model="row.price" placeholder="请输入"></el-input>
</template>
</el-table-column>
<el-table-column prop="stock" label="库存" align="center">
<template #default="{ row }">
<el-input v-model="row.stock" placeholder="请输入"></el-input>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item label="参考价" prop="info.reference_price">
<el-input v-model="form.info.reference_price" placeholder="请输入" />
</el-form-item>
<el-form-item label="订单库存计数" prop="info.order_stock_count">
<el-radio-group v-model="form.info.order_stock_count">
<el-radio v-for="item in orderStockCount" :key="item.value" v-bind="item"></el-radio>
</el-radio-group>
</el-form-item>
</div>
</template>
<script setup>
import { shippingTemplate, afterSalesPolicy } from '@/utils/dictionary'
const form = inject('form')
</script>
<template>
<div>
<el-form-item label="运费模板" prop="info.shipping_template" :rules="[{ required: true, message: '请选择' }]">
<el-select v-model="form.info.shipping_template">
<el-option v-for="item in shippingTemplate" :key="item.value" v-bind="item"></el-option>
</el-select>
</el-form-item>
<el-form-item label="售后政策" prop="info.after_sales_policy" :rules="[{ required: true, message: '请选择' }]">
<el-select v-model="form.info.after_sales_policy">
<el-option v-for="item in afterSalesPolicy" :key="item.value" v-bind="item"></el-option>
</el-select>
</el-form-item>
</div>
</template>
import type { RouteRecordRaw } from 'vue-router'
import Layout from '@/components/layout/Index.vue'
const routes: RouteRecordRaw[] = [
{
path: '/live/product/management',
component: Layout,
children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'add', component: () => import('./views/Update.vue'), props: { action: 'add' } },
{ path: 'update', component: () => import('./views/Update.vue'), props: { action: 'update' } },
{ path: 'view', component: () => import('./views/Update.vue'), props: { action: 'view' } }
]
}
]
export { routes }
<script setup>
import { ElMessageBox, ElMessage } from 'element-plus'
import LiveProductCategory from '@/components/LiveProductCategory.vue'
import { getProductList, deleteProduct } from '../api'
const appList = ref(null)
// 刷新
const handleRefresh = () => {
appList.value?.refetch()
}
const listParams = reactive({ name: '', live_commodity_type_id: '' })
// 列表配置
const listOptions = computed(() => {
return {
remote: {
httpRequest: getProductList,
params: listParams,
beforeRequest(params, isReset) {
if (isReset) listParams.live_commodity_type_id = ''
params.live_commodity_type_id = listParams.live_commodity_type_id
return params
},
callback(data) {
const list = data.list?.map((item) => {
item.picture_addreses = JSON.parse(item.picture_addreses)
item.picture_url = item.picture_addreses[0]?.url
item.picture_url_list = item.picture_addreses?.map((i) => i.url)
return item
})
return { ...data, list }
},
},
filters: [
{ label: '商品品类', prop: 'live_commodity_type_id', slots: 'filter-category' },
{ label: '商品名称', prop: 'name', type: 'input' },
],
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '商品主图', prop: 'picture_addreses', slots: 'table-picture', width: 140 },
{ label: '商品标题', prop: 'title' },
{ label: '所属商品品类', prop: 'live_commodity_type_full_name' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 },
],
}
})
const handleRemove = async (row) => {
await ElMessageBox.confirm('此操作将永久删除该数据, 是否继续?', '提示')
await deleteProduct({ id: row.id })
ElMessage.success('删除成功')
appList.value?.refetch()
}
</script>
<template>
<AppCard title="商品管理">
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary">
<router-link to="management/add">添加商品</router-link>
</el-button>
</template>
<template #filter-category>
<LiveProductCategory v-model="listParams.live_commodity_type_id" @change="handleRefresh"></LiveProductCategory>
</template>
<template #table-picture="{ row }">
<el-image
:src="row.picture_url"
style="width: 100px; height: 100px"
:previewSrcList="row.picture_url_list"
preview-teleported></el-image>
</template>
<template #table-x="{ row }">
<el-button text type="primary">
<router-link :to="{ path: 'management/view', query: { id: row.id } }">查看</router-link>
</el-button>
<el-button text type="primary">
<router-link :to="{ path: 'management/view', query: { id: row.id } }">编辑</router-link>
</el-button>
<el-button text type="primary" @click="handleRemove(row)">删除</el-button>
</template>
</AppList>
</AppCard>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import FormCategory from '../components/FormCategory.vue'
import FormBaseInfo from '../components/FormBaseInfo.vue'
import FormGraphicInfo from '../components/FormGraphicInfo.vue'
import FormPrice from '../components/FormPrice.vue'
import FormService from '../components/FormService.vue'
import { createProduct, updateProduct, getProduct } from '../api'
const router = useRouter()
const route = useRoute()
const props = defineProps(['action'])
const actonMap = { add: '添加', update: '编辑', view: '查看' }
const isUpdate = computed(() => props.action === 'update')
const title = computed(() => actonMap[props.action] + '商品')
const formRef = ref()
const form = reactive({
live_commodity_type_id: '',
title: '',
shopping_guide_short_title: '',
live_commodity_attrs: {
importance_attrs: {},
unimportance_attrs: {},
},
picture_addreses: [],
picture_34_addreses: [],
video_url: '',
info: {
delivery_mode: '1', // 发货模式
delivery_time: '1', // 发货时间
reference_price: '', // 参考价格
order_stock_count: '1',
shipping_template: '', // 运费模板
after_sales_policy: '', // 售后政策
sku: [{ price: '', stock: 0 }],
},
})
provide('form', form)
async function fetchInfo() {
const res = await getProduct({ id: route.query.id })
Object.assign(form, res.data.detail, {
// live_commodity_attrs: {
// importance_attrs: Object.values(form.live_commodity_attrs.importance_attrs),
// unimportance_attrs: Object.values(form.live_commodity_attrs.unimportance_attrs),
// },
picture_addreses: JSON.parse(form.picture_addreses),
picture_34_addreses: JSON.parse(form.picture_34_addreses),
info: JSON.parse(form.info),
})
}
watchEffect(() => {
if (props.action !== 'add') fetchInfo()
})
const activeName = ref(1)
async function checkForm() {
return formRef.value && (await formRef.value.validate())
}
function handlePrev() {
activeName.value--
}
async function handleNext() {
await checkForm()
activeName.value++
}
async function handleSubmit() {
await checkForm()
const params = {
...form,
live_commodity_attrs: JSON.stringify({
importance_attrs: Object.values(form.live_commodity_attrs.importance_attrs),
unimportance_attrs: Object.values(form.live_commodity_attrs.unimportance_attrs),
}),
picture_addreses: JSON.stringify(form.picture_addreses),
picture_34_addreses: JSON.stringify(form.picture_34_addreses),
info: JSON.stringify(form.info),
}
isUpdate.value ? handleUpdate(params) : handleAdd(params)
}
// 新建
async function handleAdd(params) {
await createProduct(params)
ElMessage({ message: '创建成功', type: 'success' })
router.replace({ path: '/live/product/management' })
}
// 编辑
async function handleUpdate(params) {
await updateProduct(params)
ElMessage({ message: '修改成功', type: 'success' })
router.replace({ path: '/live/product/management' })
}
</script>
<template>
<AppCard :title="title" class="product">
<el-tabs v-model="activeName" stretch class="product-tabs">
<el-tab-pane label="商品类目" :name="1"></el-tab-pane>
<el-tab-pane label="基础信息" :name="2"></el-tab-pane>
<el-tab-pane label="图文信息" :name="3"></el-tab-pane>
<el-tab-pane label="价格库存" :name="4"></el-tab-pane>
<el-tab-pane label="服务与履约" :name="5"></el-tab-pane>
</el-tabs>
<el-row justify="center" style="min-height: 400px">
<el-col :sm="24" :md="16">
<!-- <el-card shadow="never"> -->
<el-form label-position="top" :model="form" ref="formRef">
<!-- 商品类目 -->
<FormCategory v-if="activeName === 1"></FormCategory>
<!-- 基础信息 -->
<FormBaseInfo v-if="activeName === 2"></FormBaseInfo>
<!-- 图文信息 -->
<FormGraphicInfo v-if="activeName === 3"></FormGraphicInfo>
<!-- 价格库存 -->
<FormPrice v-if="activeName === 4"></FormPrice>
<!-- 服务与履约 -->
<FormService v-if="activeName === 5"></FormService>
</el-form>
<el-row justify="center">
<el-button type="primary" plain v-if="activeName === 1" @click="$router.back()">取消</el-button>
<el-button type="primary" plain @click="handlePrev" v-if="activeName > 1">上一步</el-button>
<el-button type="primary" @click="handleNext" v-if="activeName < 5">下一步</el-button>
<el-button type="primary" @click="handleSubmit" v-if="activeName === 5">保存</el-button>
</el-row>
<!-- </el-card> -->
</el-col>
</el-row>
</AppCard>
</template>
<style lang="scss">
.product-tabs {
.el-tabs__header {
width: 600px;
margin: 0 auto 20px;
}
.el-tabs__nav-wrap::after {
display: none;
}
}
.product {
.el-card {
background-color: rgba(242, 242, 242, 0.49);
}
}
</style>
import httpRequest from '@/utils/axios'
// 获取实验直播话术的列表
export function getTalkList(params?: { name?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-speeches/list', { params })
}
// 获取实验直播话术详情
export function getTalk(params: { id: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-speeches/detail', { params })
}
// 创建实验直播话术
export function createTalk(data: { pid: string; name: string; status: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-speeches/create', data)
}
// 更新实验直播话术详情
export function updateTalk(data: { id: string; name: string; status: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-speeches/update', data)
}
// 删除实验直播话术
export function deleteTalk(data: { id: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-speeches/delete', data)
}
<script setup>
import { ElMessage } from 'element-plus'
import { liveTestDuration } from '@/utils/dictionary'
import LiveProductSelect from '@/components/LiveProductSelect.vue'
import { createTalk, updateTalk, getTalk } from '../api'
const props = defineProps(['data', 'action'])
const emit = defineEmits(['update', 'update:modelValue'])
const actonMap = { add: '添加', update: '编辑', view: '查看' }
const isUpdate = computed(() => props.action === 'update')
const title = computed(() => actonMap[props.action] + '直播话术')
const formRef = ref()
const form = reactive({
name: '',
live_commodity_id: '',
selling_point: '',
marketing_campaign: '',
duration: '10分钟',
content: '',
})
watchEffect(() => {
if (props.data) Object.assign(form, props.data)
})
const rules = ref({
name: [{ required: true, message: '请输入' }],
product_id: [{ required: true, message: '请选择' }],
selling_point: [{ required: true, message: '请输入' }],
marketing_campaign: [{ required: true, message: '请输入' }],
duration: [{ required: true, message: '请选择' }],
})
async function fetchInfo() {
const res = await getTalk({ id: props.data.id })
Object.assign(form, res.data.detail)
}
watchEffect(() => {
if (props.action !== 'add') fetchInfo()
})
// 提交
async function handleSubmit() {
await formRef.value?.validate()
isUpdate.value ? handleUpdate() : handleAdd()
}
// 新建
async function handleAdd() {
await createTalk(form)
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
// 编辑
async function handleUpdate() {
await updateTalk(form)
ElMessage({ message: '修改成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" @closed="$emit('update:modelValue', false)">
<el-row justify="center" :gutter="40">
<el-col :sm="24" :md="12">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" :disabled="action === 'view'">
<el-form-item label="直播话术名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<el-form-item label="选择商品" prop="live_commodity_id">
<LiveProductSelect v-model="form.live_commodity_id"></LiveProductSelect>
</el-form-item>
<el-form-item label="商品卖点" prop="selling_point">
<el-input type="textarea" v-model="form.selling_point" placeholder="多个卖点请使用“;”分割。"></el-input>
</el-form-item>
<el-form-item label="营销活动" prop="marketing_campaign">
<el-input
type="textarea"
v-model="form.marketing_campaign"
placeholder="多个营销活动请使用“;”分割。"></el-input>
</el-form-item>
<el-form-item label="话术时长" prop="duration">
<el-radio-group v-model="form.duration">
<el-radio v-for="item in liveTestDuration" v-bind="item" :key="item.value"></el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</el-col>
<el-col :sm="24" :md="12" style="border-left: 1px solid #dcdfe6">
<div style="text-align: center">
<h2 style="margin-bottom: 20px">直播话术</h2>
<el-button type="primary" size="large">AI生成直播话术</el-button>
</div>
</el-col>
</el-row>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" round auto-insert-space @click="handleSubmit" v-if="action !== 'view'"
>保存</el-button
>
</el-row>
</template>
</el-dialog>
</template>
import type { RouteRecordRaw } from 'vue-router'
import Layout from '@/components/layout/Index.vue'
const routes: RouteRecordRaw[] = [
{
path: '/live/talk',
component: Layout,
children: [{ path: '', component: () => import('./views/Index.vue') }]
}
]
export { routes }
<script setup>
import { ElMessageBox, ElMessage } from 'element-plus'
import LiveProductCategory from '@/components/LiveProductCategory.vue'
import { getTalkList, deleteTalk } from '../api'
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const appList = ref(null)
// 刷新
const handleRefresh = () => {
appList.value?.refetch()
}
const listParams = reactive({ name: '', live_commodity_type_id: '', live_commodity_title: '' })
// 列表配置
const listOptions = computed(() => {
return {
remote: {
httpRequest: getTalkList,
params: listParams,
beforeRequest(params, isReset) {
if (isReset) listParams.live_commodity_type_id = ''
params.live_commodity_type_id = listParams.live_commodity_type_id
return params
},
},
filters: [
{ label: '商品品类', prop: 'live_commodity_type_id', slots: 'filter-category' },
{ label: '商品名称', prop: 'live_commodity_title', type: 'input' },
{ label: '话术名称', prop: 'name', type: 'input' },
],
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '直播话术名称', prop: 'name' },
{ label: '商品标题', prop: 'title' },
{ label: '所属商品品类', prop: 'live_commodity_type_full_name' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 },
],
}
})
const action = ref(null)
const formVisible = ref(false)
const currentRow = ref(null)
// 新建
const handleAdd = () => {
action.value = 'add'
currentRow.value = null
formVisible.value = true
}
// 编辑
const handleUpdate = (row) => {
action.value = 'update'
currentRow.value = row
formVisible.value = true
}
// 查看
const handleView = (row) => {
action.value = 'view'
currentRow.value = row
formVisible.value = true
}
// 删除
const handleRemove = async (row) => {
await ElMessageBox.confirm('此操作将永久删除该数据, 是否继续?', '提示')
await deleteTalk({ id: row.id })
appList.value?.refetch()
ElMessage.success('删除成功')
}
</script>
<template>
<AppCard title="直播话术管理">
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary" @click="handleAdd">添加话术</el-button>
</template>
<template #filter-category>
<LiveProductCategory v-model="listParams.live_commodity_type_id" @change="handleRefresh"></LiveProductCategory>
</template>
<template #table-x="{ row }">
<el-button text type="primary" @click="handleView(row)">查看</el-button>
<el-button text type="primary" @click="handleUpdate(row)">编辑</el-button>
<el-button text type="primary" @click="handleRemove(row)">删除</el-button>
</template>
</AppList>
</AppCard>
<!-- 新建/修改 -->
<FormDialog v-model="formVisible" :action="action" :data="currentRow" @update="handleRefresh" v-if="formVisible" />
</template>
import httpRequest from '@/utils/axios'
// 获取实验直播练习的列表
export function getTestList(params?: { name?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-practice/list', { params })
}
// 获取实验直播练习详情
export function getTest(params: { id: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-practice/detail', { params })
}
// 创建实验直播练习
export function createTest(data: { pid: string; name: string; status: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-practice/create', data)
}
// 更新实验直播练习详情
export function updateTest(data: { id: string; name: string; status: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-practice/update', data)
}
// 删除实验直播练习
export function deleteTest(data: { id: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-practice/delete', data)
}
<script setup>
import { ElMessage } from 'element-plus'
import { liveTestDuration, liveTestUploadWay } from '@/utils/dictionary'
import LiveProductSelect from '@/components/LiveProductSelect.vue'
import { createTest, updateTest } from '../api'
const props = defineProps(['data'])
const emit = defineEmits(['update', 'update:modelValue'])
const isUpdate = computed(() => !!props.data?.id)
const title = computed(() => (isUpdate.value ? '编辑直播练习' : '添加直播练习'))
const formRef = ref()
const form = reactive({
live_commodity_id: '',
live_speech_id: '',
duration: '10分钟',
upload_way: '1',
})
const rules = ref({
live_commodity_id: [{ required: true, message: '请选择' }],
live_speech_id: [{ required: true, message: '请选择' }],
duration: [{ required: true, message: '请选择' }],
upload_way: [{ required: true, message: '请选择' }],
})
// 提交
async function handleSubmit() {
await formRef.value?.validate()
isUpdate.value ? handleUpdate() : handleAdd()
}
// 新建
async function handleAdd() {
await createTest(form)
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
// 编辑
async function handleUpdate() {
await updateTest(form)
ElMessage({ message: '修改成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" @closed="$emit('update:modelValue', false)">
<el-row justify="center">
<el-col :sm="24" :md="16">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
<el-form-item label="选择商品" prop="live_commodity_id">
<LiveProductSelect v-model="form.live_commodity_id"></LiveProductSelect>
</el-form-item>
<el-form-item label="选择直播话术" prop="live_speech_id">
<el-select v-model="form.live_speech_id" style="width: 100%" placeholder="请选择"></el-select>
</el-form-item>
<el-form-item label="选择练习时长" prop="duration">
<el-radio-group v-model="form.duration">
<el-radio v-for="item in liveTestDuration" v-bind="item" :key="item.value"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="直播练习上传方式" prop="upload_way">
<el-radio-group v-model="form.upload_way">
<el-radio v-for="item in liveTestUploadWay" v-bind="item" :key="item.value"></el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</el-col>
</el-row>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
</el-row>
</template>
</el-dialog>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
import LiveCover from './LiveCover.vue'
import LiveStream from './LiveStream.vue'
import LivePlayback from './LivePlayback.vue'
defineProps({
isView: { type: Boolean, default: false },
})
const userStore = useUserStore()
const live = ref(null)
const enabled = ref(false)
function start() {
if (live.value) {
enabled.value = true
live.value.start()
}
}
function stop() {
if (live.value) {
enabled.value = false
live.value.stop()
}
}
</script>
<template>
<div class="live">
<div class="live-hd">
<p>主播:{{ userStore.user.name }}</p>
<img :src="enabled ? '/live/live2.png' : '/live/live1.png'" style="height: 20px" v-if="!isView" />
</div>
<div class="live-bd">
<LiveStream ref="live" v-if="isView"></LiveStream>
<LivePlayback ref="live" v-else></LivePlayback>
<LiveCover v-if="enabled" />
</div>
<div class="live-ft" v-if="!isView">
<el-button type="primary" size="large" round style="width: 80%" @click="start" v-if="!enabled"
>开始练习</el-button
>
<el-button type="primary" size="large" round style="width: 80%" @click="stop" v-else>结束直播练习</el-button>
</div>
</div>
</template>
<style lang="scss">
.live {
width: 375px;
}
.live-hd {
display: flex;
justify-content: space-between;
}
.live-bd {
position: relative;
margin: 10px 0;
height: 667px;
border-radius: 8px;
overflow: hidden;
background-color: #000;
}
.live-ft {
text-align: center;
}
</style>
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
</script>
<template>
<div class="live-cover">
<div class="upperGradient"></div>
<div class="bottomGradient"></div>
<div class="header">
<div class="headerTop">
<div class="userPanel">
<div style="width: 88px; margin-right: 6px; margin-left: 10px">
<div class="whiteText">{{ userStore.user.name }}</div>
<div class="likeTip">66本场点赞</div>
</div>
<div class="followButton">关注</div>
</div>
<div style="display: flex; align-items: center">
<img src="/live/avatar/1.png" class="smallAvatar" /><img src="/live/avatar/2.png" class="smallAvatar" /><img
src="/live/avatar/3.png"
class="smallAvatar" />
<div class="grayPanel" style="margin-right: 4px">50</div>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
<g
stroke="#fff"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
filter="url(#close_svg__a)">
<path d="M5 15 15 5M5 5l10 10"></path>
</g>
<defs>
<filter
id="close_svg__a"
width="20"
height="20"
x="0"
y="0"
color-interpolation-filters="sRGB"
filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood>
<feColorMatrix
in="SourceAlpha"
result="hardAlpha"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"></feColorMatrix>
<feOffset></feOffset>
<feGaussianBlur stdDeviation="2"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0"></feColorMatrix>
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_42_157162"></feBlend>
<feBlend in="SourceGraphic" in2="effect1_dropShadow_42_157162" result="shape"></feBlend>
</filter>
</defs>
</svg>
</div>
</div>
<div class="headerDown">
<div style="display: flex">
<div class="grayPanel" style="margin-right: 8px">
<img src="/live/hot.png" style="height: 12px; margin-right: 4px" />明星大侦探
</div>
<div class="grayPanel">小时榜第 8 名</div>
</div>
<div class="downLeft">
<img src="/live/more.png" style="height: 14px; margin-right: 4px" />更多直播<svg
class="force-icon force-icon-right"
width="1em"
height="1em"
viewBox="0 0 12 12"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
style="margin-left: 4px">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.05563 8.12334C3.86036 8.3186 3.86036 8.63518 4.05563 8.83044L4.40918 9.184C4.60444 9.37926 4.92102 9.37926 5.11629 9.184L7.94471 6.35557C8.04287 6.25742 8.09168 6.12861 8.09116 5.99996C8.09168 5.87132 8.04287 5.74251 7.94471 5.64435L5.11629 2.81593C4.92102 2.62066 4.60444 2.62066 4.40918 2.81593L4.05563 3.16948C3.86036 3.36474 3.86036 3.68132 4.05563 3.87659L6.179 5.99996L4.05563 8.12334Z"></path>
</svg>
</div>
</div>
<div style="display: flex; justify-content: flex-end">
<img src="/live/game.png" style="height: 36px; margin: 8px 15px" />
</div>
</div>
<div class="bottom">
<div class="item">
<div class="topBar">
<div class="talk">
<div class="dot"></div>
讲解中
</div>
<svg
class="force-icon force-icon-close"
width="1em"
height="1em"
viewBox="0 0 49 48"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path
d="M34.0245 12.5077L36.1695 14.6527C36.8464 15.3296 36.8464 16.4272 36.1695 17.1041L29.2728 23.9996L36.1695 30.8959C36.8464 31.5728 36.8464 32.6704 36.1695 33.3473L34.0245 35.4923C33.3475 36.1692 32.25 36.1692 31.5731 35.4923L24.6749 28.5931L17.7838 35.4898C17.1069 36.1667 16.0093 36.1667 15.3324 35.4898L13.1874 33.3448C12.5105 32.6679 12.5105 31.5703 13.1874 30.8934L20.077 23.9996L13.1874 17.1066C12.5105 16.4297 12.5105 15.3321 13.1874 14.6552L15.3324 12.5102C16.0093 11.8333 17.1069 11.8333 17.7838 12.5102L24.6749 19.4017L31.5731 12.5077C32.25 11.8308 33.3475 11.8308 34.0245 12.5077Z"></path>
</svg>
</div>
<img src="/live/product_bg.png" style="width: 106px; height: 106px" />
<div class="title">手机</div>
</div>
</div>
</div>
</template>
<style lang="scss">
.live-cover {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 120;
padding-top: 14px;
padding-bottom: 14px;
display: flex;
flex-direction: column;
justify-content: space-between;
.upperGradient {
background: linear-gradient(180deg, rgba(0, 0, 0, 0.24), transparent);
height: 140px;
left: 0;
position: absolute;
top: 0;
width: 100%;
z-index: 110;
}
.bottomGradient {
background: linear-gradient(180deg, transparent 100%, rgba(0, 0, 0, 0.24) 0);
bottom: 0;
height: 140px;
left: 0;
position: absolute;
width: 100%;
z-index: 110;
}
.header {
position: relative;
z-index: 120;
}
.headerTop {
display: flex;
justify-content: space-between;
padding-left: 7px;
padding-right: 17px;
}
.userPanel {
border-radius: 120px;
padding: 2px;
width: 150px;
display: flex;
align-items: center;
background: #00000033;
}
.followButton {
display: flex;
align-items: center;
background: linear-gradient(117.68deg, #ff2593 12.72%, #ff2559 87.95%);
border-radius: 120px;
color: #fff;
font-size: 12px;
font-weight: 500;
height: 28px;
margin-right: 2px;
padding: 0 8px;
}
.whiteText {
color: #ffffffe5;
font-size: 11px;
font-weight: 500;
}
.likeTip {
color: #ffffffb2;
font-size: 9px;
font-weight: 500;
}
.smallAvatar {
border-radius: 12px;
height: 24px;
margin-right: 4px;
overflow: hidden;
width: 24px;
}
.grayPanel {
background: #00000033;
border-radius: 20px;
color: #fff;
display: flex;
font-size: 11px;
font-weight: 500;
height: 24px;
padding: 0 6px;
align-items: center;
}
.headerDown {
box-sizing: border-box;
margin-top: 8px;
padding-left: 7px;
display: flex;
align-items: center;
justify-content: space-between;
}
.downLeft {
display: flex;
align-items: center;
background: #00000033;
border-bottom-left-radius: 20px;
border-top-left-radius: 20px;
color: #fff;
font-size: 11px;
font-weight: 500;
height: 24px;
padding: 0 6px;
}
.bottom {
box-sizing: border-box;
display: flex;
padding-right: 14px;
z-index: 120;
justify-content: flex-end;
.item {
border-radius: 5px;
display: flex;
overflow: hidden;
padding-top: 2px;
position: relative;
width: 110px;
flex-direction: column;
align-items: center;
background-color: #fff;
}
.topBar {
display: flex;
height: 24px;
left: 7px;
position: absolute;
top: 6px;
width: 100px;
color: #fff;
font-weight: 500;
justify-content: space-between;
}
.talk {
background: #00000057;
border-radius: 3px;
display: flex;
font-size: 10px;
height: 15px;
padding: 0 2px;
align-items: center;
line-height: 12px;
}
.title {
box-sizing: border-box;
font-size: 12px;
padding: 6px;
width: 100%;
}
}
}
</style>
<script setup></script>
<template>
<video controls autoplay ref="video" class="live-stream-video"></video>
</template>
<style lang="scss" scoped>
.live-stream-video {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
<script setup>
import { useDevicesList, useUserMedia } from '@vueuse/core'
import { saveAs } from 'file-saver'
const { videoInputs: cameras, audioInputs: microphones } = useDevicesList({
requestPermissions: true
})
const currentCamera = computed(() => cameras.value[0]?.deviceId)
const currentMicrophone = computed(() => microphones.value[0]?.deviceId)
const { stream } = useUserMedia({
enabled: true,
constraints: {
video: { deviceId: currentCamera },
audio: { deviceId: currentMicrophone }
}
})
const video = ref()
watchEffect(() => {
if (video.value) {
video.value.srcObject = stream.value
}
})
watch(stream, value => {
if (value) {
console.log('stream changed', value)
createMediaRecorder()
}
})
let mediaRecorder = null
let recordedChunks = []
function createMediaRecorder() {
if (!stream.value) return
mediaRecorder = new MediaRecorder(stream.value, { mimeType: 'video/mp4' })
mediaRecorder.ondataavailable = event => {
if (event.data.size > 0) {
recordedChunks.push(event.data)
const reader = new FileReader()
reader.onloadend = () => {
console.log(reader.result)
const base64Data = reader.result.split(',')[2] // 去掉 Base64 前缀
console.log(base64Data)
// 构建 JSON 数据
const jsonData = JSON.stringify({
type: 'video',
payload: base64Data
})
console.log('发送视频数据', jsonData)
}
reader.readAsDataURL(event.data) // 将 Blob 转换为 Base64
}
}
// 停止录制后生成 Blob 和下载链接
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: 'video/mp4' })
saveAs(blob, 'recording.mp4')
recordedChunks = []
}
}
function start() {
if (!mediaRecorder) return
mediaRecorder.start(100)
console.log(mediaRecorder.state)
console.log('录制开始')
}
function stop() {
if (!mediaRecorder) return
mediaRecorder.stop()
console.log(mediaRecorder.state)
console.log('录制停止')
}
defineExpose({ start, stop })
</script>
<template>
<video muted autoplay ref="video" class="live-stream-video"></video>
</template>
<style lang="scss" scoped>
.live-stream-video {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
import type { RouteRecordRaw } from 'vue-router'
import Layout from '@/components/layout/Index.vue'
const routes: RouteRecordRaw[] = [
{
path: '/live',
redirect: '/live/test',
},
{
path: '/live/test',
component: Layout,
children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'demo', component: () => import('./views/Demo.vue') },
{ path: 'view', component: () => import('./views/Demo.vue'), props: { isView: true } },
],
},
]
export { routes }
<script setup>
import Live from '../components/Live.vue'
defineProps({
isView: { type: Boolean, default: false },
})
const hotList = ref(['一键开启', '高效便捷', '一键开启', '高效便捷', '一键开启', '高效便捷', '一键开启', '高效便捷'])
const actList = ref(['7天无理由退货', '7天无理由退货', '7天无理由退货', '7天无理由退货', '7天无理由退货'])
</script>
<template>
<AppCard title="直播练习" full>
<div class="live-row">
<div class="live-col"><Live isView /></div>
<div class="live-col" style="flex: 1">
<h2 class="h2-title">直播话术</h2>
<div class="live-talk-content">
<p class="t1">商品引入</p>
<p>
<span>开场互动</span>
亲爱的家人们,欢迎来到默认主播的直播间呀!今天可是有超棒的宝贝要给大家分享哦,大家有没有对电脑硬件感兴趣的呀?快来跟主播互动一下吧!
</p>
<p>
<span>引出话题</span>
说到电脑,那可是我们生活和工作中不可或缺的一部分呢。大家平时使用电脑的时候有没有遇到过卡顿、运行不流畅的情况呀?今天我带来的这款
1DIY 兼容机,就能完美解决这些问题哦!
</p>
<p>
<span>引出商品</span>
这款 1DIY
兼容机,它可是经过精心设计和配置的呢。它能够满足大家各种不同的使用需求,无论是日常办公、学习,还是玩大型游戏,都能轻松应对。
</p>
<p>
<span>目标人群场景锁定</span>
对于那些对电脑性能有较高要求的朋友们,这款兼容机绝对是你们的最佳选择。比如那些经常需要处理大量数据、进行图形设计的专业人士,或者是喜欢玩高画质游戏的游戏爱好者们,它都能给你们带来超棒的体验哦!
</p>
<p class="t1">商品讲解</p>
<p>
<span>产品卖点讲解</span>
咱们这款兼容机的第一个卖点就是它的强大性能。它配备了最新的处理器和高性能显卡,运行速度那叫一个快,让你在使用过程中感受不到丝毫卡顿。而且内存和硬盘的容量也非常大,能够存储大量的文件和数据。第二个卖点就是它的兼容性非常好,能够兼容各种软件和硬件,不用担心出现不兼容的情况。
</p>
<p>
<span>观众互动</span>
家人们,你们觉得什么样的电脑配置才是最适合自己的呢?快在评论区留言告诉主播哦,主播会根据大家的需求给大家更详细的介绍。
</p>
<p class="t1">引导转化</p>
<p>
<span>催单话术</span>
这么好的一款兼容机,大家是不是已经心动啦?别再犹豫啦,赶紧下单把它带回家吧!现在下单还有特别的优惠活动哦,错过可就太可惜啦!
</p>
<p>
<span>强调购买方式</span>
购买的方式非常简单哦,大家只需要点击屏幕下方的购买按钮,按照提示填写好收货信息就可以啦。我们会尽快安排发货,让大家尽快收到心仪的电脑。
</p>
<p>
<span>结尾互动</span>
好啦,今天的直播就到这里啦,感谢家人们的陪伴和支持。如果大家还有什么问题或者建议,都可以随时在直播间里留言哦。下次直播我们还会有更多惊喜好物等着大家,再见啦,家人们!
</p>
</div>
</div>
<div class="live-col" style="width: 350px">
<div class="live-col-box" v-if="isView">
<h2 class="h2-title">直播数据</h2>
<dl>
<dt>观众总人数:</dt>
<dd>100</dd>
</dl>
<dl>
<dt>最高峰人数:</dt>
<dd>100</dd>
</dl>
<dl>
<dt>点赞数:</dt>
<dd>9778</dd>
</dl>
<dl>
<dt>刷礼物人数:</dt>
<dd>40</dd>
</dl>
<dl>
<dt>刷礼物总数:</dt>
<dd>1200</dd>
</dl>
</div>
<div class="live-col-box" v-else>
<h2 class="h2-title">倒计时</h2>
<h3 class="live-time">14 : 55</h3>
</div>
<div class="live-col-box">
<h2 class="h2-title">商品卖点</h2>
<ul class="live-tag live-tag__hot">
<li v-for="item in hotList" :key="item">{{ item }}</li>
</ul>
</div>
<div class="live-col-box">
<h2 class="h2-title">营销活动</h2>
<ul class="live-tag live-tag__act">
<li v-for="item in actList" :key="item">{{ item }}</li>
</ul>
</div>
</div>
</div>
</AppCard>
</template>
<style lang="scss">
.live-row {
height: 100%;
display: flex;
gap: 20px;
}
.live-col {
padding: 20px;
border-radius: 10px;
border: 1px solid #eee;
.h2-title {
margin-top: 0;
}
}
.live-tag {
margin: 20px 0;
background-color: #eee;
border-radius: 10px;
padding: 20px;
gap: 10px;
li {
line-height: 30px;
background-color: #fff;
text-align: center;
border: 1px solid rgba(105, 113, 140, 0.12);
}
}
.live-time {
height: 140px;
font-size: 72px;
text-align: center;
color: var(--main-color);
}
.live-tag__hot {
display: flex;
flex-wrap: wrap;
li {
flex: 0 0 calc(50% - 10px);
}
}
.live-tag__act {
li {
margin: 10px 0;
}
}
.live-talk-content {
p {
margin: 10px 0;
line-height: 24px;
}
.t1 {
padding: 0 5px;
display: inline-block;
background-color: rgba(25, 102, 255, 0.08);
color: rgb(25, 102, 255);
border: 1px solid transparent;
}
span {
padding: 0 5px;
color: rgb(25, 102, 255);
border: 1px solid rgba(25, 102, 255, 0.12);
}
}
</style>
<script setup>
import LiveProductCategory from '@/components/LiveProductCategory.vue'
import { getTestList } from '../api'
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const appList = ref(null)
// 刷新
const handleRefresh = () => {
appList.value?.refetch()
}
const listParams = reactive({ name: '', live_commodity_type_id: '' })
// 列表配置
const listOptions = computed(() => {
return {
remote: {
httpRequest: getTestList,
params: listParams,
beforeRequest(params, isReset) {
if (isReset) listParams.live_commodity_type_id = ''
params.live_commodity_type_id = listParams.live_commodity_type_id
return params
},
},
filters: [
{ label: '商品品类', prop: 'live_commodity_type_id', slots: 'filter-category' },
{ label: '商品标题', prop: 'product_name', type: 'input' },
],
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '直播ID', prop: 'id' },
{ label: '商品标题', prop: 'product_name' },
{ label: '直播话术', prop: 'name' },
{ label: '所属商品品类', prop: 'product_category' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 },
],
data: [
{
id: 1,
product_name: '手机',
name: '手机销售',
product_category: '智能手机',
updated_time: '2021-07-30 14:59:56',
},
{
id: 1,
product_name: '手机',
name: '手机销售',
product_category: '智能手机',
updated_time: '2021-07-30 14:59:56',
},
{
id: 1,
product_name: '手机',
name: '手机销售',
product_category: '智能手机',
updated_time: '2021-07-30 14:59:56',
},
],
}
})
const formVisible = ref(false)
</script>
<template>
<AppCard title="直播练习">
<AppList v-bind="listOptions" re="appList">
<template #header-buttons>
<el-button type="primary" @click="formVisible = true">添加直播练习</el-button>
</template>
<template #filter-category>
<LiveProductCategory v-model="listParams.live_commodity_type_id" @change="handleRefresh"></LiveProductCategory>
</template>
<template #table-x="{ row }">
<el-button text type="primary">上传</el-button>
<el-button text type="primary">
<router-link :to="{ path: 'test/demo', query: { id: row.id } }">练习</router-link>
</el-button>
<el-button text type="primary">
<router-link :to="{ path: 'test/view', query: { id: row.id } }">查看</router-link>
</el-button>
</template>
</AppList>
</AppCard>
<!-- 新建/修改 -->
<FormDialog v-model="formVisible" @update="handleRefresh" v-if="formVisible" />
</template>
......@@ -123,7 +123,7 @@ function numberToChinese(num) {
let str = ''
let unitIndex = 0
while (num > 0) {
let digit = num % 10 // 取出个位数字
const digit = num % 10 // 取出个位数字
if (digit !== 0) {
// 如果不为零,则添加中文数字和单位
str = chineseNums[digit] + chineseUnits[unitIndex] + str
......
......@@ -2,7 +2,8 @@
import { Position, Handle, useVueFlow, useNode, MarkerType } from '@vue-flow/core'
import { CircleCloseFilled } from '@element-plus/icons-vue'
import NodeCustomForm from './NodeCustomForm.vue'
const Flow = defineAsyncComponent(() => import('./Flow.vue'))
import Flow from './Flow.vue'
// const Flow = defineAsyncComponent(() => import('./Flow.vue'))
const props = defineProps(['label', 'data', 'process', 'selected', 'id', 'disabled'])
const emit = defineEmits(['nodeRemove'])
......@@ -100,7 +101,9 @@ function removeNodeBetweenEdges() {
<template>
<div class="flow-node flow-node-custom" :class="{ 'is-completed': isCompleted }">
<el-icon class="flow-node-custom__remove" @click="removeNodeBetweenEdges" v-if="selected && !disabled"><CircleCloseFilled /></el-icon>
<el-icon class="flow-node-custom__remove" @click="removeNodeBetweenEdges" v-if="selected && !disabled"
><CircleCloseFilled
/></el-icon>
<Handle type="target" :position="Position.Left" />
<div class="flow-node-custom__inner">
<el-button type="primary" size="small" @click="dialogVisible = true" v-if="!disabled">编辑</el-button>
......@@ -108,7 +111,12 @@ function removeNodeBetweenEdges() {
<div class="flow-node__label">{{ data.label || label }}</div>
</div>
<Handle type="source" :position="Position.Right" />
<NodeCustomForm :id="id" :data="data" :process="process" v-model="dialogVisible" v-if="dialogVisible"></NodeCustomForm>
<NodeCustomForm
:id="id"
:data="data"
:process="process"
v-model="dialogVisible"
v-if="dialogVisible"></NodeCustomForm>
<el-dialog title="自动化营销旅程设计-二级流程" append-to-body width="1000" v-model="flowDialogVisible">
<Flow v-model:nodes="nodes" v-model:edges="edges" :process="2" :disabled="disabled" style="height: 500px"></Flow>
<template #footer>
......
......@@ -113,7 +113,7 @@ function numberToChinese(num) {
let str = ''
let unitIndex = 0
while (num > 0) {
let digit = num % 10 // 取出个位数字
const digit = num % 10 // 取出个位数字
if (digit !== 0) {
// 如果不为零,则添加中文数字和单位
str = chineseNums[digit] + chineseUnits[unitIndex] + str
......
......@@ -78,7 +78,7 @@ const listOptions = computed(() => {
}
})
function isComplete(is: Boolean) {
function isComplete(is: boolean) {
let n = ''
is ? (n = '<div style="color: #009b3b; font-size:20px;">✓</div>') : (n = '<div style="font-size:20px;">-</div>')
return n
......
......@@ -40,25 +40,25 @@ const listOptions = computed(() => {
}
})
function isComplete(is: Boolean) {
function isComplete(is: boolean) {
let n = ''
is ? (n = '<div style="color: #009b3b; font-size:20px;">✓</div>') : (n = '<div style=" font-size:20px;">-</div>')
return n
}
let editorText = ref('')
const editorText = ref('')
const dialogVisible = ref(false)
let commentId = ref()
const commentId = ref()
const handleComment = function (row: any) {
commentId = row.id
commentId.value = row.id
editorText.value = row.comment
dialogVisible.value = true
}
let handleBtn = function () {
const item = data.find((i: any) => i.id === commentId)
const handleBtn = function () {
const item = data.find((i: any) => i.id === commentId.value)
item.comment = editorText.value
editorText.value = ''
dialogVisible.value = false
......
......@@ -14,7 +14,7 @@ getSearchCriteria().then((res: { data: { experiment: ExperimentInfo } }) => {
}
})
let detail = ref<any>({})
const detail = ref<any>({})
getRecordList({ sno_number: route.query.snoNumber }).then(res => {
if (res.data) {
detail.value = res.data.list[0]
......
......@@ -16,12 +16,12 @@ const userStore = useUserStore()
* @returns {string} 签名
*/
const buildSign = obj => {
let signParameterArray = []
for (let key in obj) {
const signParameterArray = []
for (const key in obj) {
signParameterArray.push(`${key}=${obj[key]}`)
}
let signPlaintext = signParameterArray.sort().join('&')
const signPlaintext = signParameterArray.sort().join('&')
return md5(signPlaintext).toUpperCase()
}
......@@ -34,7 +34,7 @@ const buildSign = obj => {
* @returns {string} 签名
*/
const buildVersion2Sign = (appId, expireTime, userFlag, appSecret) => {
let signParameterObj = {
const signParameterObj = {
app_id: appId,
expire_time: expireTime,
user_flag: userFlag,
......@@ -66,7 +66,7 @@ function openDesignPage() {
const userFlag = userStore.user.id
const expireTime = Date.now()
const sign = buildVersion2Sign(appId, expireTime, userFlag, appSecret)
let params = {
const params = {
app_id: appId,
expire_time: expireTime,
user_flag: userFlag,
......
......@@ -120,7 +120,7 @@ function handleRefresh() {
let dialogVisible = $ref(false)
// 编辑
let currentRow = ref<MaterialProp>()
const currentRow = ref<MaterialProp>()
const handleEdit = function (row: MaterialProp) {
router.push({ path: 'material/update', query: { id: row.id } })
}
......
......@@ -65,7 +65,7 @@ function handleRefresh() {
let updateVisible = $ref(false)
// 编辑
let currentRow = ref<MaterialProp>()
const currentRow = ref<MaterialProp>()
const handleEdit = function (row: MaterialProp) {
currentRow.value = row
row.isView = false
......
......@@ -65,7 +65,7 @@ function handleRefresh() {
let updateVisible = $ref(false)
// 编辑
let currentRow = ref<MaterialProp>()
const currentRow = ref<MaterialProp>()
const handleEdit = function (row: MaterialProp) {
currentRow.value = row
row.isView = false
......
......@@ -65,7 +65,7 @@ function handleRefresh() {
let updateVisible = $ref(false)
// 编辑
let currentRow = ref<MaterialProp>()
const currentRow = ref<MaterialProp>()
const handleEdit = function (row: MaterialProp) {
currentRow.value = row
row.isView = false
......
......@@ -65,7 +65,7 @@ function handleRefresh() {
let updateVisible = $ref(false)
// 编辑
let currentRow = ref<MaterialProp>()
const currentRow = ref<MaterialProp>()
const handleEdit = function (row: MaterialProp) {
currentRow.value = row
row.isView = false
......
......@@ -65,7 +65,7 @@ function handleRefresh() {
let updateVisible = $ref(false)
// 编辑
let currentRow = ref<MaterialProp>()
const currentRow = ref<MaterialProp>()
const handleEdit = function (row: MaterialProp) {
currentRow.value = row
row.isView = false
......
......@@ -65,7 +65,7 @@ function handleRefresh() {
let updateVisible = $ref(false)
// 编辑
let currentRow = ref<MaterialProp>()
const currentRow = ref<MaterialProp>()
const handleEdit = function (row: MaterialProp) {
currentRow.value = row
row.isView = false
......
......@@ -65,7 +65,7 @@ function handleRefresh() {
let updateVisible = $ref(false)
// 编辑
let currentRow = ref<MaterialProp>()
const currentRow = ref<MaterialProp>()
const handleEdit = function (row: MaterialProp) {
currentRow.value = row
row.isView = false
......
......@@ -65,7 +65,7 @@ function handleRefresh() {
let updateVisible = $ref(false)
// 编辑
let currentRow = ref<MaterialProp>()
const currentRow = ref<MaterialProp>()
const handleEdit = function (row: MaterialProp) {
currentRow.value = row
row.isView = false
......
......@@ -64,7 +64,7 @@ function handleUpdate() {
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @closed="$emit('update:modelValue', false)">
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="140px">
<el-form-item label="事件ID" prop="id" v-if="isUpdate">
<span>{{ props.data?.id }}</span>
......@@ -76,7 +76,11 @@ function handleUpdate() {
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<el-form-item label="所属连接" prop="experiment_connection_id">
<el-select :disabled="isUpdate" v-model="form.experiment_connection_id" style="width: 100%" placeholder="请选择">
<el-select
:disabled="isUpdate"
v-model="form.experiment_connection_id"
style="width: 100%"
placeholder="请选择">
<el-option :label="item.type_name" :value="item.id" :key="item.id" v-for="item in props.option"></el-option>
</el-select>
</el-form-item>
......
......@@ -121,12 +121,7 @@ const popoverText = function (type: any) {
</script>
<template>
<el-dialog
:title="title"
:close-on-click-modal="false"
width="600px"
@update:modelValue="value => $emit('update:modelValue', value)"
>
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @closed="$emit('update:modelValue', false)">
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="122px">
<el-form-item label="属性ID" v-if="isUpdate">
{{ props.data?.id }}
......@@ -143,14 +138,12 @@ const popoverText = function (type: any) {
@change="changeFormatType"
v-model="form.type"
style="width: 100%"
placeholder="请选择"
>
placeholder="请选择">
<el-option
:label="item.label"
:value="item.value"
:key="item.id"
v-for="item in experimentAttributeOptions"
></el-option>
v-for="item in experimentAttributeOptions"></el-option>
</el-select>
</el-form-item>
<el-form-item label="属性字段格式" prop="format" v-if="form.type !== ''">
......@@ -159,8 +152,7 @@ const popoverText = function (type: any) {
v-if="form.type === '4' || form.type === '5'"
v-model="form.format"
style="width: 93%; margin-right: 10px"
placeholder="请选择"
>
placeholder="请选择">
<el-option v-if="form.type !== '5'" label="yyyy-mm-dd" value="yyyy-mm-dd"></el-option>
<el-option v-if="form.type !== '4'" label="yyyy-mm-dd hh:mm:ss" value="yyyy-mm-dd hh:mm:ss"></el-option>
</el-select>
......@@ -170,8 +162,7 @@ const popoverText = function (type: any) {
type="number"
v-else
v-model="form.format"
:placeholder="formatPlaceholder"
/>
:placeholder="formatPlaceholder" />
<el-popover placement="top-start" :width="300" trigger="hover" :title="form.type_name">
<template #reference>
<div style="display: flex; justify-content: center; cursor: pointer">
......
......@@ -20,7 +20,9 @@ const { connectionList } = useConnection()
const multipleSelection = ref<string[]>([])
function toggleSelection(data: ConnectionType) {
multipleSelection.value.includes(data.id) ? multipleSelection.value.filter(id => id !== data.id) : multipleSelection.value.push(data.id)
multipleSelection.value.includes(data.id)
? multipleSelection.value.filter(id => id !== data.id)
: multipleSelection.value.push(data.id)
}
function isActive(data: ConnectionType) {
......@@ -38,9 +40,14 @@ function handleSave() {
</script>
<template>
<el-dialog title="配置连接" width="800px" append-to-body @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog title="配置连接" width="800px" append-to-body @closed="$emit('update:modelValue', false)">
<div class="connection-list">
<div class="connection-item" v-for="item in connectionList" :key="item.id" :class="{ 'is-active': isActive(item) }" @click="toggleSelection(item)">
<div
class="connection-item"
v-for="item in connectionList"
:key="item.id"
:class="{ 'is-active': isActive(item) }"
@click="toggleSelection(item)">
<el-checkbox @change="toggleSelection(item)" :model-value="isActive(item)" />
<div class="connection-item__icon"><ConnectionIcon :name="item.type + ''" /></div>
<p>{{ item.name }}</p>
......
......@@ -47,13 +47,30 @@ function handleSave() {
</script>
<template>
<el-dialog title="配置连接" width="800px" append-to-body @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog title="配置连接" width="800px" append-to-body @closed="$emit('update:modelValue', false)">
<div class="connection-list">
<div class="connection-item" v-for="item in connectionList" :key="item.id" :class="{ 'is-active': isActive(item) }" @click="toggleSelection(item)">
<div
class="connection-item"
v-for="item in connectionList"
:key="item.id"
:class="{ 'is-active': isActive(item) }"
@click="toggleSelection(item)">
<el-checkbox @change="toggleSelection(item)" :model-value="isActive(item)" />
<div :class="isActive(item) ? 'connection-item__icon active' : 'connection-item__icon'">
<ConnectionIcon style="transform: translateY(3px)" v-if="isActive(item)" color="#fff" :name="item.type + ''" w="20" h="20" />
<ConnectionIcon style="transform: translateY(3px)" v-else :multiColor="true" :name="item.type + ''" w="20" h="20" />
<ConnectionIcon
style="transform: translateY(3px)"
v-if="isActive(item)"
color="#fff"
:name="item.type + ''"
w="20"
h="20" />
<ConnectionIcon
style="transform: translateY(3px)"
v-else
:multiColor="true"
:name="item.type + ''"
w="20"
h="20" />
</div>
<p :style="`color: ${isActive(item) && '#ba143e'}`">{{ item.name }}</p>
</div>
......
......@@ -61,7 +61,7 @@ function handleUpdate() {
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @closed="$emit('update:modelValue', false)">
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="170px">
<el-form-item label="模板名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
......@@ -72,10 +72,21 @@ function handleUpdate() {
</el-select>
</el-form-item>
<el-form-item label="旅程分值" prop="score">
<el-input-number v-model="form.score" step-strictly :min="0" :max="100" placeholder="请输入不超过100的整数数值" style="width: 100%"></el-input-number>
<el-input-number
v-model="form.score"
step-strictly
:min="0"
:max="100"
placeholder="请输入不超过100的整数数值"
style="width: 100%"></el-input-number>
</el-form-item>
<el-form-item label="是否允许学生查看解析" prop="is_view_answer">
<el-switch v-model="form.is_view_answer" active-text="允许" active-value="1" inactive-text="不允许" inactive-value="0" />
<el-switch
v-model="form.is_view_answer"
active-text="允许"
active-value="1"
inactive-text="不允许"
inactive-value="0" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch v-model="form.status" active-text="生效" active-value="1" inactive-text="失效" inactive-value="0" />
......
......@@ -56,16 +56,26 @@ async function handleSubmit() {
</script>
<template>
<el-dialog title="旅程数据规则" width="900" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog title="旅程数据规则" width="900" @closed="$emit('update:modelValue', false)">
<AppList v-bind="listOptions" ref="appList">
<template #table-event="{ row }">
<el-select v-model="row.user_event_id" :disabled="isDisabled(row.connection_type)" clearable>
<el-option v-for="item in getConnection(row.connection_type)" :key="item.id" :label="item.name" :value="item.id"></el-option>
<el-option
v-for="item in getConnection(row.connection_type)"
:key="item.id"
:label="item.name"
:value="item.id"></el-option>
</el-select>
</template>
<template #table-ratio="{ row }">
<div class="table-ratio">
<el-input-number v-model="row.ratio" :controls="false" :max="100" :min="0" :disabled="isDisabled(row.connection_type)"></el-input-number>%
<el-input-number
v-model="row.ratio"
:controls="false"
:max="100"
:min="0"
:disabled="isDisabled(row.connection_type)"></el-input-number
>%
</div>
</template>
</AppList>
......
......@@ -93,7 +93,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
}
} else {
console.log('error submit!')
return false
}
})
}
......@@ -105,7 +104,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
:title="props.data ? (props.data?.isView ? '查看用户' : '编辑用户') : '新建用户'"
:close-on-click-modal="false"
width="800px"
@update:modelValue="value => $emit('update:modelValue', value)">
@closed="$emit('update:modelValue', false)">
<el-form
:disabled="props.data?.isView"
ref="ruleFormRef"
......@@ -116,7 +115,11 @@ const submitForm = (formEl: FormInstance | undefined) => {
label-width="auto">
<el-form-item label="来源链接" prop="experiment_connection_id">
<el-select v-model="form.experiment_connection_id" style="width: 100%">
<el-option v-for="item in connectionOptions" :key="item.id" :label="item?.type_name" :value="item?.id"></el-option>
<el-option
v-for="item in connectionOptions"
:key="item.id"
:label="item?.type_name"
:value="item?.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="姓名" prop="name">
......@@ -127,7 +130,11 @@ const submitForm = (formEl: FormInstance | undefined) => {
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-select v-model="form.gender" style="width: 100%">
<el-option v-for="item in store.getMapValuesByKey('system_gender')" :key="item.id" :label="item?.label" :value="item?.value"></el-option>
<el-option
v-for="item in store.getMapValuesByKey('system_gender')"
:key="item.id"
:label="item?.label"
:value="item?.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
......@@ -142,7 +149,13 @@ const submitForm = (formEl: FormInstance | undefined) => {
placeholder="请选择"
style="width: 100%"
value-format="YYYY-MM-DD" />
<el-date-picker value-format="YYYY-MM-DD HH:mm:ss" v-else v-model="item.value" type="datetime" placeholder="请选择" style="width: 100%" />
<el-date-picker
value-format="YYYY-MM-DD HH:mm:ss"
v-else
v-model="item.value"
type="datetime"
placeholder="请选择"
style="width: 100%" />
</template>
<el-input v-else v-model="item.value" style="width: 100%" placeholder="请输入"></el-input>
</el-form-item>
......
......@@ -33,7 +33,9 @@ onMounted(() => {
getEventList().then(res => {
eventList = res.data.map(
(item: any) =>
item.attributes.map((cItem: any) => (props.data ? (cItem.value = JSON.parse(props.data.fields)[cItem.id]) : (cItem.value = '') && cItem)) && item
item.attributes.map((cItem: any) =>
props.data ? (cItem.value = JSON.parse(props.data.fields)[cItem.id]) : (cItem.value = '') && cItem
) && item
)
})
})
......@@ -76,8 +78,6 @@ const submitForm = (formEl: FormInstance | undefined) => {
ElMessage({ message: '创建成功', type: 'success' })
})
}
} else {
return false
}
})
}
......@@ -88,12 +88,18 @@ const submitForm = (formEl: FormInstance | undefined) => {
:title="!props.data ? '新建用户事件属性' : '修改用户事件属性'"
:close-on-click-modal="false"
width="500px"
@update:modelValue="value => $emit('update:modelValue', value)">
@closed="$emit('update:modelValue', false)">
<div class="update-event_info">
<span>姓名:{{ props.info?.name }}</span>
<span>来源链接:{{ props.info?.connection_name }}</span>
</div>
<el-form :disabled="props.data?.isView" ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="122px">
<el-form
:disabled="props.data?.isView"
ref="formRef"
:model="form"
:rules="rules"
label-suffix=":"
label-width="122px">
<el-form-item label="请选择事件" prop="experiment_meta_event_id">
<el-select :disabled="!!props.data" v-model="form.experiment_meta_event_id" style="width: 100%">
<el-option v-for="item in eventList" :key="item.id" :label="item.name" :value="item.id"></el-option>
......@@ -109,7 +115,13 @@ const submitForm = (formEl: FormInstance | undefined) => {
placeholder="请选择"
style="width: 100%"
value-format="YYYY-MM-DD" />
<el-date-picker value-format="YYYY-MM-DD HH:mm:ss" v-else v-model="item.value" type="datetime" placeholder="请选择" style="width: 100%" />
<el-date-picker
value-format="YYYY-MM-DD HH:mm:ss"
v-else
v-model="item.value"
type="datetime"
placeholder="请选择"
style="width: 100%" />
</template>
<el-input v-else v-model="item.value" style="width: 100%" placeholder="请输入"></el-input>
</el-form-item>
......
......@@ -35,7 +35,10 @@ const connectionName = computed(() => {
// 下载数据模板
const downloadTemplate = function () {
if (form.event_id !== '') window.open(`/api/lab/v1/experiment/member/event-download?event_id=${form.event_id}&experiment_id=${route.query.experiment_id}`)
if (form.event_id !== '')
window.open(
`/api/lab/v1/experiment/member/event-download?event_id=${form.event_id}&experiment_id=${route.query.experiment_id}`
)
}
// 上传
......@@ -66,7 +69,7 @@ const fileData = $ref<any>([])
title="导入用户事件数据"
:close-on-click-modal="false"
width="500px"
@update:modelValue="value => $emit('update:modelValue', value)">
@closed="$emit('update:modelValue', false)">
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="122px">
<el-form-item label="请选择事件" prop="event_id">
<el-select v-model="form.event_id" style="width: 100%">
......
......@@ -60,7 +60,7 @@ const fileData = $ref<any>([])
title="导入用户数据"
:close-on-click-modal="false"
width="500px"
@update:modelValue="value => $emit('update:modelValue', value)">
@closed="$emit('update:modelValue', false)">
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="142px">
<el-form-item label="请选择所属链接" prop="connection_id">
<el-select v-model="form.connection_id" style="width: 100%">
......
......@@ -62,8 +62,7 @@ const refetch = function () {
title="导入用户数据"
:close-on-click-modal="false"
width="900px"
@update:modelValue="value => $emit('update:modelValue', value)"
>
@closed="$emit('update:modelValue', false)">
<AppList v-bind="listOptions" ref="appList">
<template #table-x="{ row }">
<el-button type="primary" plain @click="refetch" v-permission="'v1-experiment-member-delete'">刷新</el-button>
......
......@@ -96,7 +96,7 @@ const userVisible = $ref(false)
let updateVisible = $ref(false)
// 新建、更新、查看
let progressVisible = $ref(false)
const progressVisible = $ref(false)
let multipleSelection = $ref<MemberProp[]>([])
function handleSelectionChange(selection: MemberProp[]) {
......@@ -153,7 +153,7 @@ const deleteMembers = function (params: any) {
}
// 编辑
let currentRow = ref<MemberProp>()
const currentRow = ref<MemberProp>()
const handleEdit = function (row: MemberProp) {
currentRow.value = row
row.isView = false
......
......@@ -22,6 +22,12 @@ import IconMiniProgram from '@/components/icon/IconMiniProgram.vue'
import IconCard from '@/components/icon/IconCard.vue'
import IconEvent from '@/components/icon/IconEvent.vue'
import IconMarket from '@/components/icon/IconMarket.vue'
import IconLive from '@/components/icon/IconLive.vue'
import IconLiveProductCategory from '@/components/icon/IconLiveProductCategory.vue'
import IconLiveProductAttr from '@/components/icon/IconLiveProductAttr.vue'
import IconLiveProductManagement from '@/components/icon/IconLiveProductManagement.vue'
import IconLiveTalk from '@/components/icon/IconLiveTalk.vue'
import IconLiveTest from '@/components/icon/IconLiveTest.vue'
interface State {
studentMenus: IMenuItem[]
......@@ -109,7 +115,12 @@ const studentMenus: IMenuItem[] = [
// tag: 'v1-experiment-marketing-material-list'
tag: ''
},
{ name: 'H5资料管理', path: '/material?type=5', icon: markRaw(IconH5), tag: 'v1-experiment-marketing-material-list' },
{
name: 'H5资料管理',
path: '/material?type=5',
icon: markRaw(IconH5),
tag: 'v1-experiment-marketing-material-list'
},
{
name: '二维码资料管理',
path: '/material?type=6',
......@@ -131,6 +142,16 @@ const studentMenus: IMenuItem[] = [
path: '/trip/my',
icon: markRaw(IconTrip)
},
{
name: '直播带货',
path: '/live',
icon: markRaw(IconLive),
children: [
{ name: '商品管理', path: '/live/product/management', icon: markRaw(IconLiveProductManagement) },
{ name: '直播话术管理', path: '/live/talk', icon: markRaw(IconLiveTalk) },
{ name: '直播练习', path: '/live/test', icon: markRaw(IconLiveTest) }
]
},
{
name: '数据分析',
path: '/analyze',
......@@ -236,7 +257,12 @@ const adminMenus: IMenuItem[] = [
icon: markRaw(IconVideo),
tag: 'v1-experiment-marketing-material-list'
},
{ name: 'H5资料管理', path: '/material?type=5', icon: markRaw(IconH5), tag: 'v1-experiment-marketing-material-list' },
{
name: 'H5资料管理',
path: '/material?type=5',
icon: markRaw(IconH5),
tag: 'v1-experiment-marketing-material-list'
},
{
name: '二维码资料管理',
path: '/material?type=6',
......@@ -258,6 +284,18 @@ const adminMenus: IMenuItem[] = [
tag: 'experiment_itinerary'
// children: [{ name: '旅程模板管理', path: '/trip/template', tag: 'experiment_itinerary_list' }]
},
{
name: '直播带货',
path: '/live',
icon: markRaw(IconLive),
children: [
{ name: '商品品类管理', path: '/live/product/category', icon: markRaw(IconLiveProductCategory) },
{ name: '商品属性管理', path: '/live/product/attr', icon: markRaw(IconLiveProductAttr) },
{ name: '商品管理', path: '/live/product/management', icon: markRaw(IconLiveProductManagement) },
{ name: '直播话术管理', path: '/live/talk', icon: markRaw(IconLiveTalk) },
{ name: '直播练习', path: '/live/test', icon: markRaw(IconLiveTest) }
]
},
{
name: '数据分析',
path: '/analyze',
......
......@@ -4,26 +4,26 @@ export interface Dictionary {
}
export function getNameByValue(value: string | number, list: Dictionary[]) {
return list.find(item => item.value == value)?.label || value
return list.find((item) => item.value == value)?.label || value
}
// 旅程类型
export const tripTemplateTypeList = [
{ label: '自由旅程', value: '1' },
{ label: '固定旅程', value: '2' }
{ label: '固定旅程', value: '2' },
]
// 群组类型
export const groupTypeList = [
{ label: '静态群组', value: '1' },
{ label: '动态群组', value: '2' },
{ label: 'RFM群组', value: '3' }
{ label: 'RFM群组', value: '3' },
]
// 更新方式
export const updateStatusRuleList = [
{ label: '自动更新', value: '1' },
{ label: '手动更新', value: '2' }
{ label: '手动更新', value: '2' },
]
// 更新状态
......@@ -31,13 +31,13 @@ export const updateStatusList = [
{ label: '未开始', value: '1' },
{ label: '进行中', value: '2' },
{ label: '完成', value: '3' },
{ label: '进行中', value: '4' }
{ label: '进行中', value: '4' },
]
export const dateUnitList = [
{ label: '天', value: 1 },
{ label: '周', value: 2 },
{ label: '月', value: 3 }
{ label: '月', value: 3 },
]
export const weekList = [
......@@ -47,7 +47,7 @@ export const weekList = [
{ label: '周四', value: 4 },
{ label: '周五', value: 5 },
{ label: '周六', value: 6 },
{ label: '周日', value: 7 }
{ label: '周日', value: 7 },
]
export interface OperatorType {
......@@ -63,7 +63,7 @@ export const stringOperatorList: OperatorType[] = [
{ label: '包含', value: 'in' },
{ label: '不包含', value: 'not in' },
{ label: '空值', value: 'null' },
{ label: '非空', value: 'not null' }
{ label: '非空', value: 'not null' },
]
// 整数|数字
......@@ -76,14 +76,14 @@ export const numberOperatorList: OperatorType[] = [
{ label: '<=', value: '<=', alias: '≤' },
{ label: '区间', value: 'range' },
{ label: '空值', value: 'null' },
{ label: '非空', value: 'not null' }
{ label: '非空', value: 'not null' },
]
// 日期
export const dateOperatorList: OperatorType[] = [
{ label: '绝对时间之前', value: 'before' },
{ label: '绝对时间之后', value: 'after' },
{ label: '绝对时间区间', value: 'range' }
{ label: '绝对时间区间', value: 'range' },
// { label: '相对过去天数', value: 'relative_past_day' },
// { label: '相对当前天数', value: 'relative_current_day' },
// { label: '在...天内', value: 'in_day' }
......@@ -91,7 +91,7 @@ export const dateOperatorList: OperatorType[] = [
export const happenInfoList = [
{ label: '发生过', value: true },
{ label: '未发生过', value: false }
{ label: '未发生过', value: false },
]
export const triggerInfoList = [{ label: '触发次数', value: '触发次数' }]
......@@ -103,19 +103,19 @@ export const labelList = [
{ label: '事件指标标签 ', value: '3' },
{ label: '自定义标签', value: '7' },
{ label: '分层标签 ', value: '1' },
{ label: 'RFM模型标签 ', value: '4' }
{ label: 'RFM模型标签 ', value: '4' },
]
export const wayList = [
{ label: '总金额 ', value: '1' },
{ label: '总次数 ', value: '2' },
{ label: '平均金额 ', value: '3' }
{ label: '平均金额 ', value: '3' },
]
export const materialMethodList = [
{ label: '离线上传 ', value: '2' },
{ label: '在线AI', value: '1' },
{ label: '在线设计', value: '3' }
{ label: '在线设计', value: '3' },
]
// 使用场景
......@@ -123,7 +123,7 @@ export const materialUsageList = [
{ label: '用户拉新 ', value: '1' },
{ label: '产品宣传 ', value: '2' },
{ label: '用户关怀 ', value: '3' },
{ label: '系统通知 ', value: '4' }
{ label: '系统通知 ', value: '4' },
]
// 使用人员
......@@ -132,7 +132,7 @@ export const materialUsersList = [
{ label: '品牌人员 ', value: '2' },
{ label: '客服人员 ', value: '3' },
{ label: '运维人员 ', value: '4' },
{ label: '机器系统 ', value: '5' }
{ label: '机器系统 ', value: '5' },
]
// 图片风格
......@@ -146,12 +146,88 @@ export const materialPictureStyleList = [
{ label: '大气渐变 ', value: '7' },
{ label: '手绘水彩风 ', value: '8' },
{ label: '时尚杂志风 ', value: '9' },
{ label: '漫画风格 ', value: '10' }
{ label: '漫画风格 ', value: '10' },
]
// 文本用途
export const textPurposeList = [
{ label: '消息/短息', value: '1' },
{ label: '长文本/文章 ', value: '2' },
{ label: '短视频脚本 ', value: '3' }
{ label: '短视频脚本 ', value: '3' },
]
// 直播练习时长
export const liveTestDuration = [
{ label: '10分钟', value: '10分钟' },
{ label: '15分钟 ', value: '15分钟' },
{ label: '20分钟 ', value: '20分钟' },
]
// 直播练习时长
export const liveTestUploadWay = [
{ label: '实时上传', value: '1' },
{ label: '本地上传', value: '2' },
]
// 重要性
export const importType = [
{ label: '非重要', value: '0' },
{ label: '重要', value: '1' },
]
// 必要性
export const requiredType = [
{ label: '非必需', value: '0' },
{ label: '必需', value: '1' },
]
// 发货模式
export const deliveryMode = [
{ label: '现货发货模式', value: '1' },
{ label: '现货 + 预售发货模式', value: '2' },
]
// 发货时效
export const deliveryTime = [
{ label: '当日发/次日发', value: '1' },
{ label: '次日发', value: '2' },
{ label: '48小时内发货', value: '3' },
{ label: '3天内发货', value: '4' },
{ label: '5天内发货', value: '5' },
{ label: '7天内发货', value: '6' },
{ label: '10天内发货', value: '7' },
{ label: '15天内发货', value: '8' },
]
// 订单库存计数
export const orderStockCount = [
{ label: '下单减库存', value: '1' },
{ label: '付款减库存', value: '2' },
]
// 运费模板
export const shippingTemplate = [
{ label: '阶梯计价运费', value: '1' },
{ label: '固定运费', value: '2' },
{ label: '卖家包邮', value: '3' },
{ label: '指定条件包邮', value: '4' },
{ label: '限制买家下单区域', value: '5' },
{ label: '按件数计价', value: '6' },
{ label: '按重量计价', value: '7' },
{ label: '指定地区运费', value: '8' },
{ label: '指定地区禁止下单', value: '9' },
{ label: '全国包邮模板', value: '10' },
{ label: '自选快递运费模板', value: '11' },
]
// 售后政策
export const afterSalesPolicy = [
{ label: '7天无理由退换货', value: '1' },
{ label: '全额退款', value: '2' },
{ label: '仅退款政策', value: '3' },
{ label: '预付邮费标签', value: '4' },
{ label: '全球购物保障计划', value: '5' },
{ label: '无忧退换货', value: '6' },
{ label: '闪电退货', value: '7' },
{ label: '售后补寄', value: '8' },
]
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["auto-imports.d.ts", "env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"allowJs": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": ["element-plus/global", "@vue-macros/reactivity-transform/macros-global"]
}
}
{
"extends": "@vue/tsconfig/tsconfig.json",
"include": ["auto-imports.d.ts", "env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"allowJs": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": ["element-plus/global", "@vue-macros/reactivity-transform/macros-global"]
},
"files": [],
"references": [
{
"path": "./tsconfig.config.json"
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}
......@@ -3,6 +3,11 @@
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}
......@@ -17,9 +17,9 @@ export default defineConfig(({ mode }) => ({
AutoImport({
imports: ['vue', 'vue-router', '@vueuse/core'],
dts: true,
eslintrc: { enabled: true }
eslintrc: { enabled: true },
}),
ReactivityTransform()
ReactivityTransform(),
// checker({ vueTsc: true, eslint: { lintCommand: 'eslint "./src/**/*.{vue,js,jsx,ts,tsx}"' } })
],
server: {
......@@ -35,12 +35,17 @@ export default defineConfig(({ mode }) => ({
// changeOrigin: true,
// rewrite: path => path.replace(/^\/api\/resource/, '')
// },
'/api': 'https://saas-dml.ezijing.com'
}
'/api': 'https://saas-dml.ezijing.com',
},
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
css: {
preprocessorOptions: {
scss: { api: 'modern-compiler' },
},
},
}))
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论