【招生小程序】 优化# 主账号:团队业务逻辑处理

master
kaeery 2025-03-04 21:08:37 +08:00
parent 839a51027c
commit 329ad167a5
20 changed files with 967 additions and 211 deletions

View File

@ -17,71 +17,40 @@
lineColor="#0E66FB"
@change="handleChangeTab"
></u-tabs>
<TTable :columns="columns" :data="data">
<template #index="scope">
<text class="px-[16rpx] py-[6rpx] rounded-[4px]" :style="rankStyle(scope.row)">
{{ scope.row }}
</text>
</template>
</TTable>
<template v-if="loading && !data.length">
<view class="min-h-[200rpx] flex justify-center items-center">
<u-loading-icon></u-loading-icon>
</view>
</template>
<template v-if="!loading && data.length > 0">
<TTable :columns="columns" :data="data">
<template #index="scope">
<text class="px-[16rpx] py-[6rpx] rounded-[4px]" :style="rankStyle(scope.row)">
{{ scope.row }}
</text>
</template>
</TTable>
</template>
</view>
</template>
<script setup lang="ts">
import { computed, ref, shallowRef } from 'vue'
import { useRank } from '@/hooks/useCommon'
import { watch } from 'vue'
const tabs = shallowRef([
{ name: '电销业绩排行榜', value: 1 },
{ name: '招生业绩排行榜', value: 2 }
])
const activeTab = ref(1)
const totalLabel = computed(() => (activeTab.value == 1 ? '意向' : '成交'))
const { tabs, activeTab, columns, data, loading, rankStyle, handleChangeTab, fetchData } = useRank({
width: 160
})
const handleChangeTab = item => {
activeTab.value = item.value
generateColumns()
}
const generateColumns = () => {
return [
{ name: 'index', label: '排名', slot: true },
{ name: 'name', label: '姓名' },
{ name: 'total', label: totalLabel.value + '客户数', width: 160, align: 'right' }
]
}
const columns = ref(generateColumns())
const data = [
{
index: 1,
name: '王小虎1789789789789789',
total: 100
},
{
index: 2,
name: '王小虎2',
total: 20
},
{
index: 3,
name: '王小虎2',
total: 20
},
{
index: 4,
name: '王小虎2',
total: 20
}
]
const rankStyle = computed(() => row => {
const styleMap: Record<number, string> = {
'1': '#FCAE3C',
'2': '#BCC1D8',
'3': '#EEB286'
}
return {
color: row >= 4 ? '#3d3d3d' : '#fff',
background: styleMap[row]
watch(
() => activeTab.value,
(val: number) => {
console.log(val)
}
)
fetchData()
defineExpose({
fetchData
})
</script>
<style scoped></style>

View File

@ -149,7 +149,7 @@ export function getRangeDate(v) {
// 本周
} else if (v === '-7') {
const weekStart = new Date(nowYear, nowMonth, nowDay - nowWeekDay + 1)
const weekEnd = new Date(nowTime + oneDay) // 今
const weekEnd = new Date(nowYear, nowMonth, nowDay - nowWeekDay + 7) // 本周
dateRange.start = formatTime(weekStart, 'y-m-d')
dateRange.end = formatTime(weekEnd, 'y-m-d')
// 上周
@ -161,7 +161,7 @@ export function getRangeDate(v) {
// 本月
} else if (v === '-30') {
const monthStart = new Date(nowYear, nowMonth, 1)
const monthEnd = new Date(nowTime + oneDay)
const monthEnd = new Date(nowYear, nowMonth + 1, 0) //本月
dateRange.start = formatTime(monthStart, 'y-m-d')
dateRange.end = formatTime(monthEnd, 'y-m-d')
// 上月

View File

@ -0,0 +1,215 @@
<template>
<view class="da-dropdown-daterange-box">
<view class="da-dropdown-daterange">
<view class="da-dropdown-daterange--date" :class="daterange.start ? 'is-actived' : ''">
<picker mode="date" :value="daterange.start" @change="handleStartDate">
{{ daterange.start || '请选择日期' }}
</picker>
</view>
<view class="da-dropdown-daterange--separate"></view>
<view class="da-dropdown-daterange--date" :class="daterange.end ? 'is-actived' : ''">
<picker
mode="date"
:value="daterange.end"
:disabled="!daterange.start"
:start="daterange.start"
@change="handleEndDate"
>
{{ daterange.end || '请选择日期' }}
</picker>
</view>
</view>
<view class="da-dropdown-daterange-tags" v-if="dropdownItem.showQuick">
<block v-for="(tag, tagi) in dateTagList" :key="tagi">
<view
class="da-dropdown-tag"
:class="datetag === tag.value ? 'is-actived' : ''"
@click="handleTagDate(tag.value)"
>
<text class="da-dropdown-tag--text">{{ tag.label }}</text>
</view>
</block>
</view>
<dropdown-footer @reset="handleReset" @confirm="handleConfirm" />
</view>
</template>
<script>
import { defineComponent, ref, watch } from 'vue'
import { deepClone, getRangeDate } from '@/components/da-dropdown/utils'
import picker from './picker.vue'
import dropdownFooter from '../widgets/admin/dropdown-footer.vue'
export default defineComponent({
components: { picker, dropdownFooter },
props: {
dropdownItem: {
type: Object,
default: null
},
dropdownIndex: {
type: Number
}
},
emits: ['success', 'reset', 'confirm'],
setup(props, { emit }) {
const daterange = ref(null)
const datetag = ref('')
const dateTagList = ref([
{ value: '-7', label: '本周' },
{ value: '-14', label: '上周' },
{ value: '-30', label: '本月' },
{ value: '-60', label: '上月' },
// { value: '-1', label: '' },
{ value: '7', label: '近7天' },
{ value: '15', label: '近15天' },
{ value: '30', label: '近30天' }
])
function initData(dropdownItem, clearValue = false) {
const item = deepClone(dropdownItem || null)
if (clearValue === true) {
daterange.value = {
start: '',
end: ''
}
datetag.value = ''
} else {
daterange.value = {
start: item.value?.start || '',
end: item.value?.end || ''
}
}
}
function handleStartDate(item) {
daterange.value.start = item.detail.value
daterange.value.end = ''
datetag.value = ''
}
function handleEndDate(item) {
if (!daterange.value?.start) {
return
}
daterange.value.end = item.detail.value
datetag.value = ''
}
function handleTagDate(code) {
daterange.value = getRangeDate(code)
datetag.value = code
}
function handleReset() {
initData(props.dropdownItem, true)
emit('reset')
}
function handleConfirm() {
if (props.dropdownItem?.prop) {
const res = { [props.dropdownItem.prop]: deepClone(daterange.value) }
emit('success', res, daterange.value, props.dropdownIndex)
emit('confirm', daterange.value)
} else {
console.error(`菜单项${props.dropdownItem.title}未定义prop返回内容失败`)
}
}
watch(
() => props.dropdownItem,
v => {
initData(v)
},
{ immediate: true }
)
return {
daterange,
datetag,
dateTagList,
handleStartDate,
handleEndDate,
handleTagDate,
handleReset,
handleConfirm
}
}
})
</script>
<style lang="scss" scoped>
.da-dropdown-daterange-box {
display: flex;
flex-direction: column;
gap: 24rpx;
}
//
.da-dropdown-daterange {
display: flex;
align-items: center;
// margin: 24rpx;
background-color: #f5f5f5;
border-radius: 999rpx;
&--date {
flex-grow: 1;
height: 66rpx;
padding: 0 24rpx;
font-size: 26rpx;
line-height: 66rpx;
color: var(--dropdown-text-color);
text-align: center;
border-radius: 4rpx;
&.is-actived {
color: $blue-1;
}
}
&--separate {
flex-shrink: 0;
padding: 0 20rpx;
}
&-tags {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
padding: 0 24rpx;
}
}
.da-dropdown-tag {
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 40rpx;
margin-right: 20rpx;
margin-bottom: 20rpx;
overflow: hidden;
font-size: 28rpx;
color: var(--dropdown-text-color);
background-color: #f5f5f5;
border-radius: 999rpx;
&--text {
position: relative;
z-index: 1;
}
&.is-actived {
color: $blue-1;
background-color: #fff;
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
content: '';
background-color: $blue-1;
opacity: 0.05;
}
}
}
</style>

View File

@ -0,0 +1,222 @@
<template>
<view class="da-dropdown-picker" v-if="viewCol && viewCol.length">
<view class="da-dropdown-picker-inner" v-for="(vc, vci) in viewCol" :key="vci">
<scroll-view class="da-dropdown-picker-view" scroll-y>
<view
class="da-dropdown-picker-item"
:class="vr.checked ? 'is-actived' : ''"
v-for="(vr, vri) in viewRow[vci]"
:key="vri"
@click="handleSelect(vr, vci, vri)"
>
<text class="da-dropdown-picker-item--name">{{ vr.label }}</text>
<text
class="da-dropdown-picker-item--icon"
v-if="vr.children && vr.children.length"
></text>
<text
class="da-dropdown-picker-item--check"
v-if="vr.checked && (!vr.children || vr.children.length === 0)"
/>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import { defineComponent, ref, watch } from 'vue'
import { deepClone } from '@/components/da-dropdown/utils'
export default defineComponent({
props: {
dropdownItem: {
type: Object,
default: null
},
dropdownIndex: {
type: Number
}
},
emits: ['success'],
setup(props, { emit }) {
const viewCol = ref([])
const viewRow = ref([])
function checkData(selected, list) {
for (let i = 0; i < list.length; i++) {
const k = list[i]
for (let j = 0; j < selected.length; j++) {
const x = selected[j]
if (k.value === x) {
k.checked = true
viewCol.value.push(k.value)
viewRow.value.push(list)
if (k.children?.length) {
checkData(selected, k.children)
}
break
}
}
}
}
function initData(item) {
const list = deepClone(item?.options || [])
if (list?.length) {
if (item.value?.length) {
viewCol.value = []
viewRow.value = []
checkData(item.value, list)
} else {
viewCol.value.push('tmpValue')
viewRow.value.push(list)
}
} else {
viewCol.value = []
viewRow.value = []
}
}
function handleSelect(item, colIndex, _rowIndex) {
let lastItem = false
viewCol.value.splice(colIndex)
viewCol.value[colIndex] = item.value
if (viewRow.value[colIndex]?.length) {
viewRow.value[colIndex].forEach(k => {
k.checked = false
})
}
item.checked = true
const list = item?.children || null
if (list?.length) {
viewCol.value[colIndex + 1] = 'tmpValue'
viewRow.value[colIndex + 1] = list
lastItem = false
} else {
console.warn('最后一项', item)
lastItem = true
}
try {
if (viewRow.value[colIndex + 1]?.length) {
viewRow.value[colIndex + 1].forEach(k => {
k.checked = false
})
}
} catch (e) {
console.warn('try clean row data', e)
// --
}
if (lastItem) {
if (props.dropdownItem?.prop) {
const res = { [props.dropdownItem.prop]: deepClone(viewCol.value) }
// emit('success', res, viewCol.value, props.dropdownIndex)
} else {
console.error(`菜单项${props.dropdownItem.title}未定义prop返回内容失败`)
}
}
}
watch(
() => props.dropdownItem,
v => {
initData(v)
},
{ immediate: true }
)
return {
viewCol,
viewRow,
handleSelect
}
}
})
</script>
<style lang="scss" scoped>
.da-dropdown-picker {
display: flex;
width: 100%;
max-height: 60vh;
overflow: hidden;
line-height: 1;
&-inner {
flex-grow: 1;
}
&-view {
display: flex;
/* #ifdef MP-ALIPAY */
flex-direction: column;
flex-wrap: wrap;
/* #endif */
width: 100%;
height: 100%;
+ .da-dropdown-picker-view {
border-left: 1px solid #eee;
}
}
&-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx;
font-size: 24rpx;
color: var(--dropdown-text-color);
text-align: left;
&--icon {
width: 24rpx;
height: 24rpx;
&::after {
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
font-family: 'da-dropdown-iconfont' !important;
font-size: 24rpx;
font-style: normal;
content: '\e643';
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
&--check {
flex-shrink: 0;
width: 24rpx;
height: 24rpx;
&::after {
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
font-family: 'da-dropdown-iconfont' !important;
font-size: 24rpx;
font-style: normal;
content: '\e696';
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
&:hover {
background: #eee;
}
&.is-actived {
color: var(--dropdown-theme-color);
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<view class="flex flex-col bg-white rounded-[16rpx] mb-[24rpx]">
<view class="flex flex-col bg-white rounded-[16rpx]" :class="addClasses">
<text class="font-bold p-[24rpx]" v-if="title">{{ title }}</text>
<view>
<slot />
@ -8,11 +8,20 @@
</template>
<script setup lang="ts">
defineProps({
import { computed } from 'vue'
const props = defineProps({
title: {
type: String,
default: ''
},
className: {
type: String,
default: ''
}
})
const addClasses = computed(() => {
return props.className
})
</script>
<style scoped></style>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,77 @@
<template>
<view class="da-dropdown-footer">
<view class="da-dropdown-footer--reset" @click="handleReset()">
{{ resetText || '重置' }}
</view>
<view class="da-dropdown-footer--confirm" @click="handleConfirm()">
{{ confirmText || '确定' }}
</view>
</view>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'PartDropdownFooter',
props: {
resetText: {
type: String,
default: '重置'
},
confirmText: {
type: String,
default: '确定'
}
},
emits: ['confirm', 'reset'],
setup(_, { emit }) {
function handleReset() {
emit('reset')
}
function handleConfirm() {
emit('confirm')
}
return {
handleReset,
handleConfirm
}
}
})
</script>
<style lang="scss" scoped>
.da-dropdown-footer {
display: flex;
align-items: center;
padding: 24rpx;
margin-top: 20rpx;
&--reset,
&--confirm {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
height: 72rpx;
font-size: 28rpx;
color: #555;
background-color: #fff;
border: 2rpx solid #ccc;
border-radius: 66rpx;
}
&--confirm {
margin-left: 24rpx;
color: #fff;
background-color: $blue-1;
border-color: $blue-1;
}
&--reset:hover,
&--confirm:hover {
opacity: 0.8;
}
}
</style>

View File

@ -22,13 +22,16 @@
</scroll-view>
</view>
</view>
<dropdown-footer @reset="handleReset" @confirm="handleConfirm" />
</template>
<script>
import { deepClone } from '@/components/da-dropdown/utils'
import { defineComponent, ref, watch } from 'vue'
import dropdownFooter from './dropdown-footer.vue'
export default defineComponent({
components: { dropdownFooter },
props: {
dropdownItem: {
type: Object,
@ -38,10 +41,11 @@ export default defineComponent({
type: Number
}
},
emits: ['success'],
emits: ['success', 'reset', 'confirm'],
setup(props, { emit }) {
const viewCol = ref([])
const viewRow = ref([])
const selectedNode = ref()
function checkData(selected, list) {
for (let i = 0; i < list.length; i++) {
@ -98,8 +102,10 @@ export default defineComponent({
viewRow.value[colIndex + 1] = list
lastItem = false
} else {
console.warn('最后一项', item)
// console.warn('', item)
lastItem = true
selectedNode.value = item
emit('success', item)
}
try {
@ -122,6 +128,22 @@ export default defineComponent({
}
}
}
function handleReset() {
viewRow.value.forEach(row => {
row.forEach(item => {
item.checked = false
if (item.children && item.children.length > 0) {
item.children.forEach(child => {
child.checked = false
})
}
})
})
emit('reset')
}
function handleConfirm() {
emit('confirm', selectedNode.value)
}
watch(
() => props.dropdownItem,
@ -136,7 +158,9 @@ export default defineComponent({
viewCol,
viewRow,
handleSelect
handleSelect,
handleReset,
handleConfirm
}
}
})

View File

@ -1,5 +1,5 @@
<template>
<card>
<card className="mb-3">
<view class="flex flex-col mb-[24rpx]" @click="handleCardClick">
<view class="flex items-center p-[24rpx] gap-[24rpx]">
<view

View File

@ -1,5 +1,5 @@
<template>
<card>
<card className="mb-3">
<view class="flex flex-col mb-[24rpx]" @click="handleCardClick">
<view class="flex items-center p-[24rpx] gap-[24rpx]">
<view

View File

@ -71,7 +71,7 @@
import { ref } from 'vue'
import positionTabs from './components/position-tabs.vue'
import DaDropdown from '@/components/da-dropdown/index.vue'
import DropdownPicker from './components/orga-picker.vue'
import DropdownPicker from '../orga-picker.vue'
import telesaleCard from './components/telesale-card.vue'
import recruitsaleCard from './components/recruitsale-card.vue'
import { useZPaging } from '@/hooks/useZPaging'

View File

@ -1,20 +1,27 @@
<template>
<view class="px-[32rpx]">
<card title="线索转化情况分析">
<view class="px-[32rpx]">
<view
class="flex flex-col gap-[16rpx] mb-[24rpx]"
v-for="(item, index) in data"
:key="`unique_${index}`"
>
<text class="text-gray5">{{ item.label }}</text>
<u-line-progress
:percentage="item.value"
activeColor="#5783FE"
shape="square"
></u-line-progress>
<template v-if="loading && !data.length">
<view class="min-h-[200rpx] flex justify-center items-center">
<u-loading-icon></u-loading-icon>
</view>
</view>
</template>
<template v-if="!loading && data.length > 0">
<view class="px-[32rpx]">
<view
class="flex flex-col gap-[16rpx] mb-[24rpx]"
v-for="(item, index) in data"
:key="`unique_${index}`"
>
<text class="text-gray5">{{ item.label }}</text>
<u-line-progress
:percentage="item.value"
activeColor="#5783FE"
shape="square"
></u-line-progress>
</view>
</view>
</template>
</card>
</view>
</template>
@ -22,13 +29,29 @@
<script setup lang="ts">
import { ref } from 'vue'
import card from '../../card.vue'
const data = ref([
{ label: '待领取', value: 28 },
{ label: '转化中', value: 12 },
{ label: '已添加', value: 53 },
{ label: '异常待处理', value: 56 },
{ label: '已成交', value: 12 },
{ label: '已战败', value: 100 }
])
interface ICluseStatus {
label: string
value: number
}
const data = ref<ICluseStatus[]>([])
const loading = ref(false)
const fetchData = () => {
loading.value = true
setTimeout(() => {
data.value = [
{ label: '待领取', value: 28 },
{ label: '转化中', value: 12 },
{ label: '已添加', value: 53 },
{ label: '异常待处理', value: 56 },
{ label: '已成交', value: 12 },
{ label: '已战败', value: 100 }
]
loading.value = false
})
}
defineExpose({
fetchData
})
</script>
<style scoped></style>

View File

@ -1,39 +1,63 @@
<template>
<view class="px-[32rpx]">
<card v-for="(item, index) in data" :key="`unique_${index}`" :title="item.name">
<view class="flex bg-gray3 mx-[24rpx] mb-[24rpx]">
<view
v-for="(itemy, indey) in item.children"
:key="indey"
class="flex-1 flex flex-col gap-[12rpx] justify-center items-center py-[12rpx]"
>
<text class="text-muted">{{ itemy.label }}</text>
<text class="font-bold">{{ itemy.value }}</text>
</view>
<template v-if="loading && !data.length">
<view class="min-h-[200rpx] flex justify-center items-center">
<u-loading-icon></u-loading-icon>
</view>
</template>
<card v-for="(item, index) in data" :key="`unique_${index}`" :title="item.name">
<template v-if="!loading && data.length > 0">
<view class="flex bg-gray3 mx-[24rpx] mb-[24rpx]">
<view
v-for="(itemy, indey) in item.children"
:key="indey"
class="flex-1 flex flex-col gap-[12rpx] justify-center items-center py-[12rpx]"
>
<text class="text-muted">{{ itemy.label }}</text>
<text class="font-bold">{{ itemy.value }}</text>
</view>
</view>
</template>
</card>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import card from '../../card.vue'
const data = [
{
name: '湛江团队',
children: [
{ label: '线索', value: '52个' },
{ label: '成交客户', value: '52个' },
{ label: '转化率', value: '15%' }
interface IConvertedData {
name: string
children: Array<{ label: string; value: string }>
}
const data = ref<IConvertedData[]>([])
const loading = ref(false)
const fetchData = () => {
loading.value = true
setTimeout(() => {
data.value = [
{
name: '湛江团队',
children: [
{ label: '线索', value: '52个' },
{ label: '成交客户', value: '52个' },
{ label: '转化率', value: '15%' }
]
},
{
name: '广州团队',
children: [
{ label: '线索', value: '52个' },
{ label: '成交客户', value: '52个' },
{ label: '转化率', value: '15%' }
]
}
]
},
{
name: '广州团队',
children: [
{ label: '线索', value: '52个' },
{ label: '成交客户', value: '52个' },
{ label: '转化率', value: '15%' }
]
}
]
loading.value = false
}, 300)
}
defineExpose({
fetchData
})
</script>
<style scoped></style>

View File

@ -1,16 +1,23 @@
<template>
<view class="flex flex-col gap-[24rpx] px-[32rpx]">
<card title="数据简报">
<view class="flex flex-wrap">
<view
class="flex flex-col w-1/3 justify-center items-center gap-[12rpx] mb-[20rpx]"
v-for="(item, index) in data"
:key="`unique_${index}`"
>
<text class="text-muted">{{ item.label }}</text>
<text class="font-bold text-[40rpx]">{{ item.value }}</text>
<template v-if="loading && !data.length">
<view class="min-h-[200rpx] flex justify-center items-center">
<u-loading-icon></u-loading-icon>
</view>
</view>
</template>
<template v-else-if="!loading && data.length > 0">
<view class="flex flex-wrap">
<view
class="flex flex-col w-1/3 justify-center items-center gap-[12rpx] mb-[20rpx]"
v-for="(item, index) in data"
:key="`unique_${index}`"
>
<text class="text-muted">{{ item.label }}</text>
<text class="font-bold text-[40rpx]">{{ item.value }}</text>
</view>
</view>
</template>
</card>
</view>
</template>
@ -19,13 +26,28 @@
import { ref } from 'vue'
import card from '../../card.vue'
const data = ref([
{ label: '新增跟进', value: 368 },
{ label: '新增客户', value: 368 },
{ label: '成交客户', value: 368 },
{ label: '转化中客户', value: 368 },
{ label: '异常待处理', value: 368 },
{ label: '战败客户', value: 368 }
])
interface DataItem {
label: string
value: number
}
const loading = ref(false)
const data = ref<DataItem[]>([])
const fetchData = () => {
loading.value = true
setTimeout(() => {
data.value = [
{ label: '新增跟进', value: 368 },
{ label: '新增客户', value: 368 },
{ label: '成交客户', value: 368 },
{ label: '转化中客户', value: 368 },
{ label: '异常待处理', value: 368 },
{ label: '战败客户', value: 368 }
]
loading.value = false
}, 500)
}
defineExpose({
fetchData
})
</script>
<style scoped></style>
<style scoped lang="scss"></style>

View File

@ -18,84 +18,56 @@
lineColor="#0E66FB"
@change="handleChangeTab"
></u-tabs>
<TTable :columns="columns" :data="data">
<template #index="scope">
<text class="px-[16rpx] py-[6rpx] rounded-[4px]" :style="rankStyle(scope.row)">
{{ scope.row }}
</text>
</template>
</TTable>
<view
class="flex justify-center py-[20rpx] border-t border-solid border-border"
@click="handleMore"
>
<text>查看更多</text>
</view>
<template v-if="loading && !data.length">
<view class="min-h-[200rpx] flex justify-center items-center">
<u-loading-icon></u-loading-icon>
</view>
</template>
<template v-if="!loading && data.length > 0">
<TTable :columns="columns" :data="data">
<template #index="scope">
<text
class="px-[16rpx] py-[6rpx] rounded-[4px]"
:style="rankStyle(scope.row)"
>
{{ scope.row }}
</text>
</template>
</TTable>
<view
class="flex justify-center py-[20rpx] border-t border-solid border-border"
@click="handleMore"
>
<text>查看更多</text>
</view>
</template>
</card>
</view>
</template>
<script setup lang="ts">
import { ref, shallowRef } from 'vue'
import { watch } from 'vue'
import card from '../../card.vue'
import { computed } from 'vue'
import { useRank } from '@/hooks/useCommon'
const activeTab = ref(1)
const tabs = shallowRef([
{ name: '电销TOP5', value: 1 },
{ name: '招生TOP5', value: 2 }
])
const totalLabel = computed(() => (activeTab.value == 1 ? '意向' : '成交'))
const handleChangeTab = item => {
activeTab.value = item.value
columns.value = generateColumns()
}
const generateColumns = () => {
return [
{ name: 'index', label: '排名', slot: true },
{ name: 'name', label: '姓名' },
{ name: 'total', label: totalLabel.value + '客户数', width: 120, align: 'right' }
]
}
const columns = ref(generateColumns())
const data = [
{
index: 1,
name: '王小虎1789789789789789',
total: 100
},
{
index: 2,
name: '王小虎2',
total: 20
},
{
index: 3,
name: '王小虎2',
total: 20
},
{
index: 4,
name: '王小虎2',
total: 20
}
]
const rankStyle = computed(() => row => {
const styleMap: Record<number, string> = {
'1': '#FCAE3C',
'2': '#BCC1D8',
'3': '#EEB286'
}
return {
color: row >= 4 ? '#3d3d3d' : '#fff',
background: styleMap[row]
}
const { tabs, activeTab, columns, data, loading, rankStyle, handleChangeTab, fetchData } = useRank({
width: 120
})
watch(
() => activeTab.value,
(val: number) => {
console.log(val)
}
)
const handleMore = () => {
uni.navigateTo({
url: '/bundle/pages/rank-list/index'
})
}
defineExpose({
fetchData
})
</script>
<style scoped></style>

View File

@ -1,16 +1,108 @@
<template>
<view class="flex flex-col gap-[24rpx] mb-[20rpx]">
<data-overview />
<converted-overview />
<rank />
<clue-status />
<view class="bg-white">
<u-dropdown ref="uDropdownRef" menu-icon="arrow-down-fill" :isFlex="false">
<u-dropdown-item title="组织">
<view class="bg-white">
<DropdownPicker
:dropdownItem="dropdownMenuList"
@reset="() => handleReset('organization')"
@confirm="value => handleConfirm('organization', value)"
/>
</view>
</u-dropdown-item>
<u-dropdown-item title="日期">
<view class="bg-white p-[20rpx]">
<dateDropdownPicker
:dropdownItem="dropdownMenuList2"
@reset="() => handleReset('date')"
@confirm="value => handleConfirm('date', value)"
/>
</view>
</u-dropdown-item>
</u-dropdown>
</view>
<data-overview ref="dataOverviewRef" />
<converted-overview ref="convertedOverviewRef" />
<rank ref="rankRef" />
<clue-status ref="clueStatusRef" />
</view>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import dataOverview from './components/data-overview.vue'
import convertedOverview from './components/converted-overview.vue'
import rank from './components/rank.vue'
import clueStatus from './components/clue-status.vue'
import DropdownPicker from '../orga-picker.vue'
import dateDropdownPicker from '@/components/date-dropdown/daterange.vue'
const clueStatusRef = ref<InstanceType<typeof clueStatus>>()
const rankRef = ref<InstanceType<typeof rank>>()
const convertedOverviewRef = ref<InstanceType<typeof convertedOverview>>()
const dataOverviewRef = ref<InstanceType<typeof dataOverview>>()
const uDropdownRef = ref()
const dropdownMenuList = [
{
label: '级联X1',
value: '1',
children: [
{
label: '级联X11',
value: '11'
},
{ label: '级联X12', value: '12' }
]
},
{
label: '级联X2',
value: '2',
children: [
{ label: '级联X21', value: '21' },
{ label: '级联X22', value: '22' }
]
}
]
const dropdownMenuList2 = {
showQuick: true,
title: '日期范围',
type: 'daterange',
prop: 'god6'
// 2022-01-012022-02-01
// value: { start: '2022-01-01', end: '2022-02-01' },
}
const handleConfirm = (type: string, item) => {
switch (type) {
case 'organization':
console.log(item)
break
case 'date':
console.log(item)
break
default:
break
}
}
const handleReset = (type: string) => {
switch (type) {
case 'organization':
break
case 'date':
break
default:
break
}
}
const closeDropDown = () => {
uDropdownRef.value.close()
}
onMounted(() => {
dataOverviewRef.value?.fetchData()
convertedOverviewRef.value?.fetchData()
rankRef.value?.fetchData()
clueStatusRef.value?.fetchData()
})
</script>
<style scoped></style>

View File

@ -1,5 +1,5 @@
import { apiCluseDetail } from '@/api/clue'
import { ref } from 'vue'
import { computed, ref, shallowRef } from 'vue'
// 获取线索详情
export function useClueDetail() {
@ -19,3 +19,98 @@ export function useClueDetail() {
fetchClueDetail
}
}
interface IRank {
index: number
name: string
total: number
}
interface IRankTab {
name: string
value: number
id: number
}
// 主账号排行榜
export function useRank({ width }: { width: number }) {
const tabs = ref<IRankTab[]>([])
const activeTab = ref()
const loading = ref(false)
const data = ref<IRank[]>([])
const totalLabel = computed(() => (activeTab.value == 5 ? '意向' : '成交'))
const rankStyle = computed(() => row => {
const styleMap: Record<number, string> = {
'1': '#FCAE3C',
'2': '#BCC1D8',
'3': '#EEB286'
}
return {
color: row >= 4 ? '#3d3d3d' : '#fff',
background: styleMap[row]
}
})
const handleChangeTab = item => {
activeTab.value = item.id
generateColumns()
}
const generateColumns = () => {
return [
{ name: 'index', label: '排名', slot: true },
{ name: 'name', label: '姓名' },
{ name: 'total', label: totalLabel.value + '客户数', width, align: 'right' }
]
}
const columns = ref(generateColumns())
const fetchTabs = () => {
return new Promise(resolve => {
tabs.value = [
{ name: '电销业绩排行榜', value: 1, id: 5 },
{ name: '招生业绩排行榜', value: 2, id: 6 }
]
if (tabs.value.length > 0) activeTab.value = tabs.value[0]
resolve(tabs.value)
})
}
const fetchData = async () => {
await fetchTabs()
console.log(activeTab.value)
loading.value = true
setTimeout(() => {
data.value = [
{
index: 1,
name: '王小虎1789789789789789',
total: 100
},
{
index: 2,
name: '王小虎2',
total: 20
},
{
index: 3,
name: '王小虎2',
total: 20
},
{
index: 4,
name: '王小虎2',
total: 20
}
]
loading.value = false
}, 500)
}
return {
fetchData,
rankStyle,
activeTab,
columns,
tabs,
data,
loading,
handleChangeTab
}
}

View File

@ -9,7 +9,7 @@
lineColor="#0E66FB"
@change="handleChangeTab"
></u-tabs>
<view class="flex-1 bg-gray3 pt-[32rpx]">
<view class="flex-1 bg-gray3">
<admin-team v-if="activeTab === AdminTabEnum.TEAM" />
<personally v-if="activeTab === AdminTabEnum.PERSONALLY" />
</view>
@ -22,7 +22,7 @@ import { AdminTabEnum } from '@/enums'
import adminTeam from '@/components/widgets/admin/team/index.vue'
import personally from '@/components/widgets/admin/personally/index.vue'
const activeTab = ref(AdminTabEnum.PERSONALLY)
const activeTab = ref(AdminTabEnum.TEAM)
const tabs = shallowRef([
{ name: '团队', value: AdminTabEnum.TEAM },
{ name: '个人', value: AdminTabEnum.PERSONALLY }

View File

@ -1,6 +1,7 @@
<template>
<TContainer></TContainer>
<TProfile :isShow="false"></TProfile>
</template>
<script setup lang="ts"></script>
<style scoped></style>
<style scoped lang="scss"></style>

View File

@ -56,6 +56,10 @@ export const props = defineMixin({
menuIconSize: {
type: [Number, String],
default: 14
},
isFlex: {
type: Boolean,
default: true
}
}
})

View File

@ -11,6 +11,7 @@
>
<view
class="u-dropdown__menu__item"
:style="itemStyle"
v-for="(item, index) in menuList"
:key="index"
@tap.stop="menuClick(index)"
@ -122,6 +123,12 @@ export default {
style['transition-duration'] = this.duration / 1000 + 's'
style.borderRadius = `0 0 ${addUnit(this.borderRadius)} ${addUnit(this.borderRadius)}`
return style
},
itemStyle() {
let style = {}
style.flex = this.isFlex ? 1 : ''
style.minWidth = this.isFlex ? '' : '25%'
return style
}
},
created() {
@ -232,7 +239,7 @@ export default {
height: 80rpx;
&__item {
flex: 1;
// flex: 1;
@include flex;
justify-content: center;
align-items: center;