提交 90d00e72 authored 作者: lihuihui's avatar lihuihui

update

上级 fa868381
...@@ -202,7 +202,6 @@ ...@@ -202,7 +202,6 @@
"usePreferredDark": true, "usePreferredDark": true,
"usePreferredLanguages": true, "usePreferredLanguages": true,
"usePreferredReducedMotion": true, "usePreferredReducedMotion": true,
"usePrevious": true,
"useRafFn": true, "useRafFn": true,
"useRefHistory": true, "useRefHistory": true,
"useResizeObserver": true, "useResizeObserver": true,
......
...@@ -203,7 +203,6 @@ declare global { ...@@ -203,7 +203,6 @@ declare global {
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useRafFn: typeof import('@vueuse/core')['useRafFn'] const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
......
...@@ -123,7 +123,7 @@ function handleUpdate() { ...@@ -123,7 +123,7 @@ function handleUpdate() {
v-model="scope.row.format" v-model="scope.row.format"
placeholder="请选择" placeholder="请选择"
> >
<el-option label="yyyy-mm-dd" value="yyyy-mm-dd"></el-option> <el-option v-if="scope.row.type !== '5'" label="yyyy-mm-dd" value="yyyy-mm-dd"></el-option>
<el-option <el-option
v-if="scope.row.type !== '4'" v-if="scope.row.type !== '4'"
label="yyyy-mm-dd hh:mm:ss" label="yyyy-mm-dd hh:mm:ss"
......
import httpRequest from '@/utils/axios'
// 人员列表
export function getMemberList(params: { name?: string; id?: string; mobile?: string; status?: string; page?: number; page_size?: number }) {
return httpRequest.get('/api/experiment/v1/experiment/member/list', { params })
}
// 链接列表
export function getMemberConnectionsList() {
return httpRequest.get('/api/experiment/v1/experiment/member/connections')
}
// 用户属性
export function getMemberFieldsList() {
return httpRequest.get('/api/experiment/v1/experiment/member/member-fields')
}
// 新建用户
export function createMember(data: { name: string; status: string; experiment_connection_id: string; gender: string; mobile: string; fields: string }) {
return httpRequest.post('/api/experiment/v1/experiment/member/create', data)
}
// 删除用户
export function deleteMember(data: { ids: string; }) {
return httpRequest.post('/api/experiment/v1/experiment/member/delete', data)
}
// 新建用户
export function updateMember(data: { id?: string; name: string; status: string; experiment_connection_id: string; gender: string; mobile: string; fields: string }) {
return httpRequest.post('/api/experiment/v1/experiment/member/update', data)
}
// 单人员事件列表
export function getMemberEventList(params: { id: string; page?: number; page_size?: number }) {
return httpRequest.get('/api/experiment/v1/experiment/member/member-events', { params })
}
// 事件列表
export function getEventList() {
return httpRequest.get('/api/experiment/v1/experiment/member/events')
}
// 新建事件
export function createEvent(data: { experiment_member_id: string; experiment_meta_event_id: string; fields: any }) {
return httpRequest.post('/api/experiment/v1/experiment/member/event-create', data)
}
// 更新事件
export function updateEvent(data: { id: string; fields: any }) {
return httpRequest.post('/api/experiment/v1/experiment/member/event-update', data)
}
// 删除事件
export function deleteEvent(data: { id: string }) {
return httpRequest.post('/api/experiment/v1/experiment/member/event-delete', data)
}
// 用户画像
export function getMemberImage(params: { id: string }) {
return httpRequest.get('/api/experiment/v1/experiment/member/member-image', { params })
}
// 导入事件
export function importEvent(data: { event_id: string; file: any }) {
return httpRequest.post('/api/experiment/v1/experiment/member/event-upload', data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 导入用户
export function importMember(data: { groups_id?: string; connection_id: string; file: any }) {
return httpRequest.post('/api/experiment/v1/experiment/member/member-upload', data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 用户画像
export function getMemberGroups() {
return httpRequest.get('/api/experiment/v1/experiment/member/groups')
}
\ No newline at end of file
差异被折叠。
<script setup lang="ts"> <script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage } from 'element-plus'
import { getMemberConnectionsList, getMemberFieldsList, createMember, updateMember } from '../api'
import type { ConnectionsProp, MemberFieldsProp, MemberProp } from '../types'
import { useMapStore } from '@/stores/map'
// interface Props { const store = useMapStore()
// data?: UserProp
// }
// const props = defineProps<Props>()
defineEmits<{ const ruleFormRef = ref<FormInstance>()
onMounted(() => {
getConnectionsList()
getFieldsList()
})
// 链接列表(所属链接选项)
let connectionOptions = $ref<ConnectionsProp[]>()
const getConnectionsList = function () {
getMemberConnectionsList().then((res: { data: ConnectionsProp[] }) => {
connectionOptions = res.data
})
}
// 用户属性
let fieldsList = $ref<MemberFieldsProp[]>([])
const getFieldsList = function () {
getMemberFieldsList().then((res: { data: MemberFieldsProp[] }) => {
fieldsList = res.data.map(item => {
item.value = ''
if (props.data) {
item.value = JSON.parse(props.data?.fields)[item.id]
}
return item
})
})
}
interface Props {
data?: MemberProp
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update'): void (e: 'update'): void
(e: 'update:modelValue', visible: boolean): void (e: 'update:modelValue', visible: boolean): void
}>() }>()
const formRef = $ref<FormInstance>() const form = $ref(
const form = reactive({ props.data || {
id: '', name: '',
name: '', status: '0',
type: '', experiment_connection_id: '',
value: '', gender: '',
status: '' mobile: ''
}) }
)
const rules = ref<FormRules>({ const rules = ref<FormRules>({
type: [{ required: true, message: '请选择属性字段类型' }] name: [{ required: true, message: '请输入' }],
status: [{ required: true, message: '请选择' }],
experiment_connection_id: [{ required: true, message: '请选择' }],
gender: [{ required: true, message: '请选择' }],
mobile: [{ required: true, message: '请输入' }]
}) })
// 提交
const submitForm = (formEl: FormInstance | undefined) => {
const fields = fieldsList.reduce((a: any, b: { id: string; value: string }) => {
a[b.id] = b.value
return a
}, {})
const params = { ...form, fields: fields }
if (!formEl) return
formEl.validate(valid => {
if (valid) {
if (props.data?.id) {
updateMember(params).then(res => {
emit('update')
emit('update:modelValue', false)
ElMessage({ message: '更新成功', type: 'success' })
})
} else {
createMember(params).then(res => {
emit('update')
emit('update:modelValue', false)
ElMessage({ message: '添加成功', type: 'success' })
})
}
} else {
console.log('error submit!')
return false
}
})
}
</script> </script>
<template> <template>
<el-dialog <el-dialog
class="connect-form" class="connect-form"
title="新建用户" :title="props.data ? (props.data?.isView ? '查看用户' : '编辑用户') : '新建用户'"
:close-on-click-modal="false" :close-on-click-modal="false"
width="800px" width="800px"
@update:modelValue="$emit('update:modelValue')" @update:modelValue="$emit('update:modelValue')"
> >
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="142px"> <el-form
<el-form-item label="所属实验" prop="type"> :disabled="props.data?.isView"
<el-select v-model="form.type" style="width: 100%"></el-select> ref="ruleFormRef"
style="width: 400px; margin: 0 auto; padding: 0 30px 30px 0"
:model="form"
:rules="rules"
label-suffix=":"
label-width="110px"
>
<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" :label="item?.type_name" :value="item?.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" style="width: 100%"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="来源链接"> <el-form-item label="状态" prop="status">
<el-select v-model="form.id" style="width: 100%"></el-select> <el-switch v-model="form.status" active-text="生效" active-value="1" inactive-text="失效" inactive-value="0" />
</el-form-item> </el-form-item>
<el-form-item label="姓名"> <el-form-item label="性别" prop="gender">
<el-input v-model="form.id" style="width: 100%"></el-input> <el-select v-model="form.gender" style="width: 100%">
<el-option
v-for="item in store.getMapValuesByKey('system_gender')"
:label="item?.label"
:value="item?.value"
></el-option>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="状态"> <el-form-item label="手机号码" prop="mobile">
<el-radio-group v-model="form.id" class="ml-4"> <el-input v-model="form.mobile" style="width: 100%"></el-input>
<el-radio label="1" size="large">生效</el-radio>
<el-radio label="2" size="large">失效</el-radio>
</el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="性别"> <el-form-item :label="item.name" v-for="item in fieldsList">
<el-input v-model="form.id" style="width: 100%"></el-input> <template v-if="item.type === '4' || item.type === '5'">
<el-date-picker
v-if="item.format === 'yyyy-mm-dd'"
v-model="item.value"
type="date"
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%"
/>
</template>
<el-input v-else v-model="item.value" style="width: 100%" placeholder="请输入"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="手机号"> <el-form-item>
<el-input v-model="form.id" style="width: 100%"></el-input> <el-button type="primary" @click="submitForm(ruleFormRef)">提交</el-button>
<el-button @click="$emit('update:modelValue', false)">关闭</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="btn-box"> <!-- <div class="btn-box">
<el-button type="primary">保存</el-button> <el-button type="primary">保存</el-button>
<el-button type="primary">关闭</el-button> <el-button type="primary">关闭</el-button>
</div> </div> -->
</el-dialog> </el-dialog>
</template> </template>
<style lang="scss"> <style lang="scss">
......
<script setup lang="ts"> <script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { getEventList, createEvent, updateEvent } from '../api'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import type { EventProp, MemberProp, AttributesProp } from '../types'
// interface Props { const route = useRoute()
// data?: UserProp
// }
// const props = defineProps<Props>()
defineEmits<{ interface Props {
info?: MemberProp
data?: AttributesProp
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update'): void (e: 'update'): void
(e: 'update:modelValue', visible: boolean): void (e: 'update:modelValue', visible: boolean): void
}>() }>()
const formRef = $ref<FormInstance>() const formRef = $ref<FormInstance>()
const form = reactive({ const form = $ref(
id: '', props.data || {
name: '', experiment_meta_event_id: ''
type: '', }
value: '', )
status: ''
})
const rules = ref<FormRules>({ const rules = ref<FormRules>({
type: [{ required: true, message: '请选择属性字段类型' }] experiment_meta_event_id: [{ required: true, message: '请选择' }]
}) })
let eventList = $ref<EventProp[]>()
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
)
})
})
// 属性
const eventAttributes = computed(() => {
return eventList?.find(item => item.id === form.experiment_meta_event_id)?.attributes
})
// 提交
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
const attributes = eventAttributes.value?.reduce((a: any, b: any) => {
a[b.id] = b.value
return a
}, {})
console.log(eventList, '123attributes')
const [data] = eventAttributes.value || []
formEl.validate(valid => {
if (valid) {
if (props.data) {
const params = {
id: props.data?.id,
fields: attributes
}
updateEvent(params).then(res => {
emit('update')
emit('update:modelValue', false)
ElMessage({ message: '更新成功', type: 'success' })
})
} else {
const params = {
experiment_member_id: route.query?.user_id as '',
experiment_meta_event_id: data.experiment_meta_event_id,
fields: attributes
}
createEvent(params).then(res => {
emit('update')
emit('update:modelValue', false)
ElMessage({ message: '创建成功', type: 'success' })
})
}
} else {
return false
}
})
}
</script> </script>
<template> <template>
...@@ -34,21 +93,49 @@ const rules = ref<FormRules>({ ...@@ -34,21 +93,49 @@ const rules = ref<FormRules>({
@update:modelValue="$emit('update:modelValue')" @update:modelValue="$emit('update:modelValue')"
> >
<div class="update-event_info"> <div class="update-event_info">
<span>姓名:王小二</span> <span>姓名:{{ props.info?.name }}</span>
<span>来源链接:抖音</span> <span>来源链接:{{ props.info?.connection_name }}</span>
</div> </div>
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="122px"> <el-form
<el-form-item label="请选择事件" prop="type"> :disabled="props.data?.isView"
<el-select v-model="form.type" style="width: 100%"></el-select> 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" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="订单金额" prop="type"> <!-- 属性字段 -->
<el-input v-model="form.type" style="width: 100%"></el-input> <el-form-item :label="item.name" v-for="item in eventAttributes">
<template v-if="item.type === '4' || item.type === '5'">
<el-date-picker
v-if="item.format === 'yyyy-mm-dd'"
v-model="item.value"
type="date"
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%"
/>
</template>
<el-input v-else v-model="item.value" style="width: 100%" placeholder="请输入"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm(formRef)">提交</el-button>
<el-button @click="$emit('update:modelValue', false)">关闭</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="btn-box">
<el-button type="primary">关闭</el-button>
<el-button type="primary">保存</el-button>
</div>
</el-dialog> </el-dialog>
</template> </template>
<style lang="scss"> <style lang="scss">
......
<script setup lang="ts"> <script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { getEventList, importEvent } from '../api'
import type { EventProp } from '../types'
// interface Props { // interface Props {
// data?: UserProp // data?: UserProp
// } // }
// const props = defineProps<Props>() // const props = defineProps<Props>()
defineEmits<{ const route = useRoute()
const emit = defineEmits<{
(e: 'update'): void (e: 'update'): void
(e: 'update:modelValue', visible: boolean): void (e: 'update:modelValue', visible: boolean): void
}>() }>()
const formRef = $ref<FormInstance>() const formRef = $ref<FormInstance>()
const form = reactive({ const form = reactive({
id: '', event_id: ''
name: '',
type: '',
value: '',
status: ''
}) })
const rules = ref<FormRules>({ const rules = ref<FormRules>({
type: [{ required: true, message: '请选择属性字段类型' }] event_id: [{ required: true, message: '请选择' }]
})
// 选择事件
let eventList = $ref<EventProp[]>()
onMounted(() => {
getEventList().then(res => {
eventList = res.data
})
})
const connectionName = computed(() => {
return eventList?.find((item: EventProp) => item.id === form.event_id)?.connection_name
}) })
// 下载数据模板
const downloadTemplate = function () {
if (form.event_id !== '')
window.open(
`/api/experiment/v1/experiment/member/event-download?event_id=${form.event_id}&experiment_id=${route.query.experiment_id}`
)
}
// 上传
const fetchFileUpload = (option: any) => {
return new Promise(() => {
importEvent({ event_id: form.event_id, file: option.file }).then(() => {
ElMessage.success('导入数据成功')
emit('update')
emit('update:modelValue', false)
// emit('update:isShowImportDialog', false)
// emit('create')
})
})
}
</script> </script>
<template> <template>
...@@ -35,24 +68,37 @@ const rules = ref<FormRules>({ ...@@ -35,24 +68,37 @@ const rules = ref<FormRules>({
@update:modelValue="$emit('update:modelValue')" @update:modelValue="$emit('update:modelValue')"
> >
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="122px"> <el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="122px">
<el-form-item label="请选择事件" prop="type"> <el-form-item label="请选择事件" prop="event_id">
<el-select v-model="form.type" style="width: 100%"></el-select> <el-select v-model="form.event_id" style="width: 100%">
<span>事件所属链接:抖音</span> <el-option v-for="item in eventList" :label="item.name" :value="item.id"></el-option>
</el-select>
<span v-if="connectionName">事件所属链接:{{ connectionName }}</span>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="btn-box"> <div class="btn-box">
<el-button type="primary">下载事件数据模板</el-button> <el-button style="margin-right: 20px" type="primary" @click="downloadTemplate">下载事件数据模板</el-button>
<el-button type="primary">上传事件数据</el-button> <el-upload
class="upload-demo"
action="#"
multiple
ref="upload"
:auto-upload="true"
:limit="1"
:http-request="fetchFileUpload"
>
<el-button type="primary">上传事件数据</el-button>
</el-upload>
</div> </div>
<div class="btn-box"> <div class="btn-box">
<el-button type="primary">关闭</el-button> <el-button type="primary" @click="$emit('update:modelValue', false)">关闭</el-button>
<!-- <el-button type="primary">上传事件数据</el-button> -->
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
<style lang="scss"> <style lang="scss">
.btn-box { .btn-box {
display: flex;
text-align: center; text-align: center;
margin-bottom: 20px; margin-bottom: 20px;
justify-content: center;
} }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { getMemberConnectionsList, getMemberGroups, importMember } from '../api'
import type { ConnectionsProp } from '../types'
// interface Props { // interface Props {
// data?: UserProp // data?: UserProp
// } // }
// const props = defineProps<Props>() // const props = defineProps<Props>()
defineEmits<{ const route = useRoute()
const emit = defineEmits<{
(e: 'update'): void (e: 'update'): void
(e: 'update:modelValue', visible: boolean): void (e: 'update:modelValue', visible: boolean): void
}>() }>()
const formRef = $ref<FormInstance>() const formRef = $ref<FormInstance>()
const form = reactive({ const form = reactive({
id: '', connection_id: '',
name: '', groups_id: ''
type: '',
value: '',
status: ''
}) })
const rules = ref<FormRules>({ const rules = ref<FormRules>({
type: [{ required: true, message: '请选择属性字段类型' }] connection_id: [{ required: true, message: '请选择' }]
})
// 链接列表
let connectionList = $ref<ConnectionsProp[]>()
// 群组列表
let groupList = $ref<{ name: string; id: string }[]>()
onMounted(() => {
getMemberConnectionsList().then(res => {
connectionList = res.data
})
getMemberGroups().then(res => {
groupList = res.data
})
}) })
// 下载数据模板
const downloadTemplate = function () {
window.open(`/api/experiment/v1/experiment/member/member-download?experiment_id=${route.query.experiment_id}`)
}
// 上传
const fetchFileUpload = (option: any) => {
return new Promise(() => {
const params = Object.assign(form, { file: option.file })
importMember(params).then(() => {
ElMessage.success('导入数据成功')
emit('update')
emit('update:modelValue', false)
// emit('update:isShowImportDialog', false)
// emit('create')
})
})
}
</script> </script>
<template> <template>
...@@ -35,16 +68,30 @@ const rules = ref<FormRules>({ ...@@ -35,16 +68,30 @@ const rules = ref<FormRules>({
@update:modelValue="$emit('update:modelValue')" @update:modelValue="$emit('update:modelValue')"
> >
<el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="142px"> <el-form ref="formRef" :model="form" :rules="rules" label-suffix=":" label-width="142px">
<el-form-item label="请选择所属链接" prop="type"> <el-form-item label="请选择所属链接" prop="connection_id">
<el-select v-model="form.type" style="width: 100%"></el-select> <el-select v-model="form.connection_id" style="width: 100%">
<el-option v-for="item in connectionList" :label="item.type_name" :value="item.id"></el-option>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="请选择静态群组"> <el-form-item label="请选择静态群组">
<el-select v-model="form.id" style="width: 100%"></el-select> <el-select v-model="form.groups_id" style="width: 100%">
<el-option v-for="item in groupList" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="btn-box"> <div class="btn-box">
<el-button type="primary">下载用户数据模板</el-button> <el-button style="margin-right: 20px" type="primary" @click="downloadTemplate">下载用户数据模板</el-button>
<el-button type="primary">上传用户数据</el-button> <el-upload
class="upload-demo"
action="#"
multiple
ref="upload"
:auto-upload="true"
:limit="1"
:http-request="fetchFileUpload"
>
<el-button type="primary">上传用户数据</el-button>
</el-upload>
</div> </div>
<div class="btn-box"> <div class="btn-box">
<el-button type="primary">关闭</el-button> <el-button type="primary">关闭</el-button>
...@@ -56,5 +103,7 @@ const rules = ref<FormRules>({ ...@@ -56,5 +103,7 @@ const rules = ref<FormRules>({
.btn-box { .btn-box {
text-align: center; text-align: center;
margin-bottom: 20px; margin-bottom: 20px;
display: flex;
justify-content: center;
} }
</style> </style>
...@@ -7,7 +7,8 @@ const routes: RouteRecordRaw[] = [ ...@@ -7,7 +7,8 @@ const routes: RouteRecordRaw[] = [
component: Layout, component: Layout,
children: [ children: [
{ path: '', component: () => import('./views/Index.vue') }, { path: '', component: () => import('./views/Index.vue') },
{ path: 'eventDetails', component: () => import('./views/EventDetails.vue') } { path: 'eventDetails', component: () => import('./views/EventDetails.vue') },
{ path: 'image', component: () => import('./views/Image.vue') }
] ]
} }
] ]
......
export interface ConnectionsProp {
id: string
type: string
type_name: string
}
export interface MemberFieldsProp {
id: string
name: string
english_name: string
type: string
type_name: string
format: string
value: string
isShow: boolean
}
export interface MemberProp {
id: string
name: string
status: string
experiment_connection_id: string
gender: string
mobile: string
fields: string
isView?: boolean
connection_name: string
status_name: string
}
export interface EventProp {
name: string
id: string
attributes: AttributesProp[]
connection_name: string
}
export interface AttributesProp {
name: string
id: string
type: string
format: string
value: string
experiment_meta_event_id: string
fields: string
connection_id: string
isView: boolean
}
export interface ImageProp {
tag: string[]
static_groups: string[]
dynamic_groups: string[]
events: {
list: {
updated_time: string
connection_type: string
connection_name: string
event_name: string
}[]
}
}
<script setup lang="ts"> <script setup lang="ts">
import { Plus, Download, Upload, Delete } from '@element-plus/icons-vue' import { ElMessageBox, ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue' import AppList from '@/components/base/AppList.vue'
import UpdateEventsDialog from '../components/UpdateEventsDialog.vue' import UpdateEventsDialog from '../components/UpdateEventsDialog.vue'
import { getMemberEventList, deleteEvent } from '../api'
import type { MemberProp, AttributesProp } from '../types'
const route = useRoute()
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
let userInfo = $ref<MemberProp>()
// 列表配置 // 列表配置
const listOptions = computed(() => { const listOptions = computed(() => {
return { return {
remote: {
httpRequest: getMemberEventList,
params: { id: route.query.user_id },
callback: (data: any) => {
userInfo = data
return data.events
}
},
columns: [ columns: [
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '事件名称', prop: 'id' }, { label: '事件名称', prop: 'event_name' },
{ label: '链接名称', prop: 'name' }, { label: '链接名称', prop: 'connection_name' },
{ label: '事件发生时间', prop: 'name' }, { label: '事件发生时间', prop: 'created_time' },
{ label: '操作', slots: 'table-x', width: 380 } { label: '操作', slots: 'table-x', width: 380 }
], ]
data: [{}, {}]
} }
}) })
...@@ -24,32 +38,69 @@ function handleRefresh() { ...@@ -24,32 +38,69 @@ function handleRefresh() {
} }
// 事件弹窗 // 事件弹窗
const eventsVisible = $ref(false) let eventsVisible = $ref(false)
// 编辑
let currentRow = $ref<AttributesProp>()
const handleEdit = function (row: AttributesProp) {
row.isView = false
currentRow = row
eventsVisible = true
}
// 删除
function handleRemove(row: AttributesProp) {
ElMessageBox.confirm('确定要删除该属性吗?', '提示').then(() => {
deleteEvent({ id: row.id }).then(res => {
ElMessage({ message: '删除成功', type: 'success' })
handleRefresh()
})
})
}
// 新建
const handleAdd = function () {
currentRow = undefined
eventsVisible = true
}
// 查看
const handleView = function (row: AttributesProp) {
row.isView = true
currentRow = row
eventsVisible = true
}
</script> </script>
<template> <template>
<AppCard title="用户事件数据"> <AppCard title="用户事件数据">
<div class="event-user_info"> <div class="event-user_info">
<span>姓名:王小二</span> <span>姓名:{{ userInfo?.name }}</span>
<span>id:11111</span> <span>id:{{ userInfo?.id }}</span>
<span>来源链接:抖音</span> <span>来源链接:{{ userInfo?.connection_name }}</span>
<span>状态:生效</span> <span>状态:{{ userInfo?.status_name }}</span>
</div> </div>
</AppCard> </AppCard>
<AppCard> <AppCard>
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList">
<template #header-buttons> <template #header-buttons>
<el-button type="primary" :icon="Plus" @click="eventsVisible = true">新建用户事件</el-button> <el-button type="primary" :icon="Plus" @click="handleAdd">新建用户事件</el-button>
</template> </template>
<template #table-x> <template #table-x="{ row }">
<el-button type="primary" plain>查看</el-button> <el-button type="primary" plain @click="handleView(row)">查看</el-button>
<el-button type="primary" plain>编辑</el-button> <el-button type="primary" plain @click="handleEdit(row)">编辑</el-button>
<el-button type="primary" plain>删除</el-button> <el-button type="primary" plain @click="handleRemove(row)">删除</el-button>
</template> </template>
</AppList> </AppList>
</AppCard> </AppCard>
<!-- 新建更新事件 --> <!-- 新建更新事件 -->
<UpdateEventsDialog v-model="eventsVisible"></UpdateEventsDialog> <UpdateEventsDialog
@update="handleRefresh()"
:data="currentRow"
v-if="eventsVisible"
:info="userInfo"
v-model="eventsVisible"
></UpdateEventsDialog>
</template> </template>
<style lang="scss"> <style lang="scss">
.event-user_info { .event-user_info {
......
<script setup lang="ts">
import { getMemberImage, getMemberFieldsList } from '../api'
import type { MemberFieldsProp, ImageProp } from '../types'
import Icon from '../components/Icon.vue'
const route = useRoute()
// 画像数据
let data = $ref<ImageProp>()
// 属性
let fieldsList = $ref<MemberFieldsProp[]>()
onMounted(() => {
// 画像
getMemberImage({ id: route.query.user_id as '' }).then(res => {
data = res.data
getFields(res.data)
})
// 属性
})
const getFields = function (data: { fields: any }) {
getMemberFieldsList().then(res => {
// fieldsList = res.data
fieldsList = res.data.map((item: MemberFieldsProp) => {
if (data.fields[item.id]) {
item.value = data.fields[item.id]
item.isShow = true
}
return item
})
})
}
// 获取上下午
const getDate = function (date: string) {
return parseInt(date.slice(date.indexOf(' '), date.indexOf(' ') + 3)) > 12 ? '下午' : '上午'
}
</script>
<template>
<div class="card-box">
<AppCard class="card" title="用户属性">
<div style="display: flex; justify-content: center; padding-right: 20px">
<el-form label-suffix=":" label-width="110px">
<template v-for="item in fieldsList">
<el-form-item v-if="item?.isShow" :label="item?.name"
><span>{{ item?.value }}</span></el-form-item
>
</template>
</el-form>
</div>
</AppCard>
<AppCard class="card" title="标签与群组">
<el-tabs class="demo-tabs">
<el-tab-pane label="用户标签">
<div class="scroll">
<el-tag class="ml-2" type="success" v-for="item in data?.tag">{{ item }}</el-tag>
</div>
</el-tab-pane>
</el-tabs>
<el-tabs class="demo-tabs">
<el-tab-pane label="静态分组">
<div class="scroll">
<el-tag class="ml-2" type="success" v-for="item in data?.static_groups">{{ item }}</el-tag>
</div>
</el-tab-pane>
</el-tabs>
<el-tabs class="demo-tabs">
<el-tab-pane label="动态分组">
<div class="scroll">
<el-tag class="ml-2" type="success" v-for="item in data?.dynamic_groups">{{ item }}</el-tag>
</div>
</el-tab-pane>
</el-tabs>
</AppCard>
<AppCard class="card" title="用户行为轨迹">
<div class="event-box" v-for="item in data?.events?.list">
<div class="date">{{ item.updated_time?.slice(0, item.updated_time.indexOf(' ')) }}</div>
<div class="event-content">
<div class="time">
{{ item.updated_time?.slice(item.updated_time.indexOf(' '), item.updated_time.length - 3) }}
{{ getDate(item.updated_time) }}
</div>
<Icon class="icon" :name="item.connection_type" w="30" h="30"></Icon>
<div class="event">
<span>"{{ item.connection_name }}"</span><span>"{{ item.event_name }}"</span>
</div>
</div>
</div>
<div class="event-box" v-for="item in data?.events?.list">
<div class="date">{{ item.updated_time?.slice(0, item.updated_time.indexOf(' ')) }}</div>
<div class="event-content">
<div class="time">
{{ item.updated_time?.slice(item.updated_time.indexOf(' '), item.updated_time.length - 3) }}
{{ getDate(item.updated_time) }}
</div>
<Icon class="icon" :name="item.connection_type" w="30" h="30"></Icon>
<div class="event">
<span>"{{ item.connection_name }}"</span><span>"{{ item.event_name }}"</span>
</div>
</div>
</div>
</AppCard>
</div>
</template>
<style lang="scss">
.ml-2 {
margin-right: 10px;
}
.card-box {
display: flex;
.card {
flex: 1;
margin-top: 0;
height: 90vh;
margin-right: 10px;
.demo-tabs {
height: 26vh;
}
}
}
.event-box {
border-bottom: 1px solid #ccc;
padding: 20px 0 5px;
.date {
font-size: 14px;
}
}
.event-content {
display: flex;
font-size: 12px;
align-items: center;
// margin-bottom: 20px;
flex-wrap: wrap;
.time {
color: #ccc;
}
.icon {
margin: 0 5px;
}
span {
color: #ba143e;
font-weight: bold;
}
}
</style>
<script setup lang="ts"> <script setup lang="ts">
import { Plus, Download, Upload, Delete } from '@element-plus/icons-vue' import { Plus, Download, Upload, Delete } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue' import AppList from '@/components/base/AppList.vue'
import UploadEventsDialog from '../components/UploadEventsDialog.vue' import { getMemberList, deleteMember } from '../api'
import UploadUserDialog from '../components/UploadUserDialog.vue' import { ElMessage } from 'element-plus'
import UpdateDialog from '../components/UpdateDialog.vue' import type { MemberProp } from '../types'
// import { getMemberMetaDetail } from '../api'
const UpdateDialog = defineAsyncComponent(() => import('../components/UpdateDialog.vue'))
const UploadEventsDialog = defineAsyncComponent(() => import('../components/UploadEventsDialog.vue'))
const UploadUserDialog = defineAsyncComponent(() => import('../components/UploadUserDialog.vue'))
const router = useRouter()
const route = useRoute()
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置 // 列表配置
const listOptions = computed(() => { const listOptions = computed(() => {
return { return {
filters: [{ type: 'input', prop: 'name', placeholder: '请输入用户姓名' }], remote: {
httpRequest: getMemberList,
params: { name: '', id: '', mobile: '', status: '' }
},
filters: [
{ type: 'input', prop: 'name', placeholder: '请输入用户姓名' },
{ type: 'input', prop: 'id', placeholder: '请输入用户ID' },
{ type: 'input', prop: 'mobile', placeholder: '请输入用户手机号' },
{
type: 'select',
prop: 'status',
placeholder: '请选择生效状态',
options: [
{ label: '有效', value: '1' },
{ label: '失效', value: '0' }
]
}
],
columns: [ columns: [
{ type: 'selection' }, { type: 'selection' },
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '用户ID', prop: 'id' }, { label: '用户ID', prop: 'id' },
{ label: '姓名', prop: 'name' }, { label: '姓名', prop: 'name' },
{ label: '性别', prop: 'name' }, { label: '性别', prop: 'gender_name' },
{ label: '手机号码', prop: 'name' }, { label: '手机号码', prop: 'mobile' },
{ label: '来源连接', prop: 'name' }, { label: '来源连接', prop: 'connection_name' },
{ label: '状态', prop: 'name' }, { label: '状态', prop: 'status_name' },
{ label: '更新人', prop: 'name' }, { label: '更新人', prop: 'updated_operator_name' },
{ label: '更新时间', prop: 'name' }, { label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 380 } { label: '操作', slots: 'table-x', width: 380 }
], ]
data: [{}, {}]
} }
}) })
...@@ -40,21 +63,99 @@ const eventsVisible = $ref(false) ...@@ -40,21 +63,99 @@ const eventsVisible = $ref(false)
const userVisible = $ref(false) const userVisible = $ref(false)
// 新建、更新、查看 // 新建、更新、查看
const updateVisible = $ref(false) let updateVisible = $ref(false)
let multipleSelection = $ref<MemberProp[]>([])
function handleSelectionChange(selection: MemberProp[]) {
multipleSelection = selection
}
// 删除
const handleRemove = function (row: { id: string }) {
deleteMembers(row.id)
}
const handleRemoves = function () {
const ids = multipleSelection
.reduce((a: any, b: any) => {
a.push(b.id)
return a
}, [])
.toString()
deleteMembers(ids)
}
const deleteMembers = function (ids: string) {
deleteMember({ ids: ids }).then(res => {
ElMessage({ message: '删除成功', type: 'success' })
handleRefresh()
})
}
// 编辑
let currentRow = ref<MemberProp>()
const handleEdit = function (row: MemberProp) {
currentRow.value = row
row.isView = false
updateVisible = true
}
// 查看
const handleView = function (row: MemberProp) {
row.isView = true
currentRow.value = row
updateVisible = true
}
// 新建
const handleAdd = function () {
updateVisible = true
currentRow.value = undefined
}
// 事件详情页
const goPage = function (row: MemberProp) {
router.push({
path: '/user/eventDetails',
query: { user_id: row.id }
})
}
// 画像
const handleImage = function (row: MemberProp) {
router.push({
path: '/user/image',
query: { user_id: row.id }
})
}
// 导出用户
const downloadMember = function (isAll?: boolean) {
const ids = multipleSelection
.reduce((a: any, b: any) => {
a.push(b.id)
return a
}, [])
.toString()
if (isAll) {
window.open(`/api/experiment/v1/experiment/member/download?experiment_id=${route.query.experiment_id}`)
} else {
window.open(`/api/experiment/v1/experiment/member/download?experiment_id=${route.query.experiment_id}&ids=${ids}`)
}
}
</script> </script>
<template> <template>
<AppCard> <AppCard>
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList" @selection-change="handleSelectionChange">
<template #header-buttons> <template #header-buttons>
<el-space> <el-space>
<el-button type="primary" :icon="Plus">新建</el-button> <el-button type="primary" :icon="Plus" @click="handleAdd">新建</el-button>
<el-dropdown> <el-dropdown>
<el-button type="primary" :icon="Download">导出</el-button> <el-button type="primary" :icon="Download">导出</el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item>全部用户数据</el-dropdown-item> <el-dropdown-item @click="downloadMember(true)">全部用户数据</el-dropdown-item>
<el-dropdown-item>勾选用户数据</el-dropdown-item> <el-dropdown-item @click="downloadMember(false)">勾选用户数据</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
...@@ -67,22 +168,24 @@ const updateVisible = $ref(false) ...@@ -67,22 +168,24 @@ const updateVisible = $ref(false)
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<el-button type="danger" plain :icon="Delete">删除</el-button> <el-button type="danger" plain :icon="Delete" :disabled="!multipleSelection.length" @click="handleRemoves()"
>删除</el-button
>
</el-space> </el-space>
</template> </template>
<template #table-x> <template #table-x="{ row }">
<el-button type="primary" plain>画像</el-button> <el-button type="primary" plain @click="handleImage(row)">画像</el-button>
<el-button type="primary" plain>查看</el-button> <el-button type="primary" plain @click="handleView(row)">查看</el-button>
<el-button type="primary" plain>编辑</el-button> <el-button type="primary" plain @click="handleEdit(row)">编辑</el-button>
<el-button type="primary" plain>删除</el-button> <el-button type="primary" plain @click="handleRemove(row)">删除</el-button>
<el-button type="primary" plain>事件</el-button> <el-button type="primary" plain @click="goPage(row)">事件</el-button>
</template> </template>
</AppList> </AppList>
</AppCard> </AppCard>
<!-- 导入事件弹窗 --> <!-- 导入事件弹窗 -->
<UploadEventsDialog v-model="eventsVisible"></UploadEventsDialog> <UploadEventsDialog @update="handleRefresh" v-if="eventsVisible" v-model="eventsVisible"></UploadEventsDialog>
<!-- 导入用户弹窗 --> <!-- 导入用户弹窗 -->
<UploadUserDialog v-model="userVisible"></UploadUserDialog> <UploadUserDialog @update="handleRefresh" v-if="userVisible" v-model="userVisible"></UploadUserDialog>
<!-- 新建、更新、查看 --> <!-- 新建、更新、查看 -->
<UpdateDialog v-model="updateVisible"></UpdateDialog> <UpdateDialog v-if="updateVisible" :data="currentRow" v-model="updateVisible" @update="handleRefresh"></UpdateDialog>
</template> </template>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论