|
|
|
|
@ -7,27 +7,27 @@
|
|
|
|
|
</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="filter-bar">
|
|
|
|
|
<view class="filter-select" @click="showFilterPicker = true">
|
|
|
|
|
<text class="filter-text">{{ currentFilterLabel }}</text>
|
|
|
|
|
<text class="filter-arrow">▼</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="filter-select" @click="showRangePicker = true">
|
|
|
|
|
<text class="filter-text">{{ currentRangeLabel }}</text>
|
|
|
|
|
<text class="filter-arrow">▼</text>
|
|
|
|
|
</view>
|
|
|
|
|
<picker mode="selector" :range="filterRange" range-key="text" :value="filterIndex" @change="onFilterChange">
|
|
|
|
|
<view class="filter-select">
|
|
|
|
|
<text class="filter-text">{{ currentFilterLabel }}</text>
|
|
|
|
|
<text class="filter-arrow">▼</text>
|
|
|
|
|
</view>
|
|
|
|
|
</picker>
|
|
|
|
|
<picker mode="selector" :range="rangeRange" range-key="text" :value="rangeIndex" @change="onRangeChange">
|
|
|
|
|
<view class="filter-select">
|
|
|
|
|
<text class="filter-text">{{ currentRangeLabel }}</text>
|
|
|
|
|
<text class="filter-arrow">▼</text>
|
|
|
|
|
</view>
|
|
|
|
|
</picker>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="currentRange === 'custom'" class="filter-date-wrap">
|
|
|
|
|
<view class="date-picker-item">
|
|
|
|
|
<up-icon name="calendar" size="18" color="#999" />
|
|
|
|
|
<up-datetime-picker ref="startPickerRef" hasInput v-model="dateRange.start" mode="datetime"
|
|
|
|
|
:placeholder="t('dashboard.startDate')" :formatter="datetimeFormatter" closeOnClickOverlay
|
|
|
|
|
@confirm="onStartDateConfirm" @close="onPickerClose" @cancel="onPickerClose" />
|
|
|
|
|
<uni-datetime-picker v-model="dateRange.start" type="datetime"
|
|
|
|
|
:placeholder="t('dashboard.startDate')" @change="onStartDateChange" />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="date-picker-item">
|
|
|
|
|
<up-icon name="calendar" size="18" color="#999" />
|
|
|
|
|
<up-datetime-picker ref="endPickerRef" hasInput v-model="dateRange.end" mode="datetime"
|
|
|
|
|
:placeholder="t('dashboard.endDate')" :formatter="datetimeFormatter" closeOnClickOverlay
|
|
|
|
|
@confirm="onEndDateConfirm" @close="onPickerClose" @cancel="onPickerClose" />
|
|
|
|
|
<uni-datetime-picker v-model="dateRange.end" type="datetime"
|
|
|
|
|
:placeholder="t('dashboard.endDate')" @change="onEndDateChange" />
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="currentFilter === 'product'" class="trend-content">
|
|
|
|
|
@ -92,11 +92,6 @@
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<up-picker :show="showFilterPicker" :columns="filterColumns" @confirm="onFilterConfirm"
|
|
|
|
|
@cancel="showFilterPicker = false" @close="showFilterPicker = false" closeOnClickOverlay />
|
|
|
|
|
|
|
|
|
|
<up-picker :show="showRangePicker" :columns="rangeColumns" @confirm="onRangeConfirm"
|
|
|
|
|
@cancel="showRangePicker = false" @close="showRangePicker = false" closeOnClickOverlay />
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
@ -126,35 +121,37 @@ const passRateChartOpts = {
|
|
|
|
|
extra: { line: { type: 'straight', width: 2, activeType: 'hollow' } }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const showFilterPicker = ref(false)
|
|
|
|
|
const showRangePicker = ref(false)
|
|
|
|
|
const startPickerRef = ref(null)
|
|
|
|
|
const endPickerRef = ref(null)
|
|
|
|
|
const currentFilter = ref('task')
|
|
|
|
|
const currentRange = ref('year')
|
|
|
|
|
const isInitialLoad = ref(true)
|
|
|
|
|
|
|
|
|
|
const filterColumns = computed(() => [
|
|
|
|
|
[
|
|
|
|
|
{ text: t('dashboard.filterTask'), value: 'task' },
|
|
|
|
|
{ text: t('dashboard.filterProduct'), value: 'product' }
|
|
|
|
|
]
|
|
|
|
|
const filterRange = computed(() => [
|
|
|
|
|
{ text: t('dashboard.filterTask'), value: 'task' },
|
|
|
|
|
{ text: t('dashboard.filterProduct'), value: 'product' }
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const filterIndex = computed(() => {
|
|
|
|
|
const idx = filterRange.value.findIndex(item => item.value === currentFilter.value)
|
|
|
|
|
return idx >= 0 ? idx : 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const currentFilterLabel = computed(() => {
|
|
|
|
|
return currentFilter.value === 'task' ? t('dashboard.filterTask') : t('dashboard.filterProduct')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const rangeColumns = computed(() => [
|
|
|
|
|
[
|
|
|
|
|
{ text: t('dashboard.rangeYear'), value: 'year' },
|
|
|
|
|
{ text: t('dashboard.rangeMonth'), value: 'month' },
|
|
|
|
|
{ text: t('dashboard.rangeWeek'), value: 'week' },
|
|
|
|
|
{ text: t('dashboard.rangeToday'), value: 'today' },
|
|
|
|
|
{ text: t('dashboard.rangeCustom'), value: 'custom' }
|
|
|
|
|
]
|
|
|
|
|
const rangeRange = computed(() => [
|
|
|
|
|
{ text: t('dashboard.rangeYear'), value: 'year' },
|
|
|
|
|
{ text: t('dashboard.rangeMonth'), value: 'month' },
|
|
|
|
|
{ text: t('dashboard.rangeWeek'), value: 'week' },
|
|
|
|
|
{ text: t('dashboard.rangeToday'), value: 'today' },
|
|
|
|
|
{ text: t('dashboard.rangeCustom'), value: 'custom' }
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const rangeIndex = computed(() => {
|
|
|
|
|
const idx = rangeRange.value.findIndex(item => item.value === currentRange.value)
|
|
|
|
|
return idx >= 0 ? idx : 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const rangeLabelMap = computed(() => ({
|
|
|
|
|
year: t('dashboard.rangeYear'),
|
|
|
|
|
month: t('dashboard.rangeMonth'),
|
|
|
|
|
@ -292,21 +289,11 @@ function transformChartData(dayTrend, rangeType) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatTimestamp(ts) {
|
|
|
|
|
const d = new Date(ts)
|
|
|
|
|
const pad = (n) => String(n).padStart(2, '0')
|
|
|
|
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function datetimeFormatter(type, value) {
|
|
|
|
|
const unitMap = { year: '年', month: '月', day: '日', hour: '时', minute: '分' }
|
|
|
|
|
return value + (unitMap[type] || '')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onFilterConfirm(e) {
|
|
|
|
|
const val = e.value[0]?.value || e.value[0]
|
|
|
|
|
function onFilterChange(e) {
|
|
|
|
|
const idx = e.detail.value
|
|
|
|
|
const val = filterRange.value[idx]?.value
|
|
|
|
|
if (!val) return
|
|
|
|
|
currentFilter.value = val
|
|
|
|
|
showFilterPicker.value = false
|
|
|
|
|
if (val === 'product') {
|
|
|
|
|
loadTrendData()
|
|
|
|
|
} else if (val === 'task') {
|
|
|
|
|
@ -314,10 +301,11 @@ function onFilterConfirm(e) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onRangeConfirm(e) {
|
|
|
|
|
const val = e.value[0]?.value || e.value[0]
|
|
|
|
|
function onRangeChange(e) {
|
|
|
|
|
const idx = e.detail.value
|
|
|
|
|
const val = rangeRange.value[idx]?.value
|
|
|
|
|
if (!val) return
|
|
|
|
|
currentRange.value = val
|
|
|
|
|
showRangePicker.value = false
|
|
|
|
|
if (val !== 'custom') {
|
|
|
|
|
const range = getDateRange(val)
|
|
|
|
|
dateRange.start = range.start
|
|
|
|
|
@ -354,33 +342,20 @@ function clearChartData() {
|
|
|
|
|
taskChartData.series = [{ name: t('dashboard.taskTrend'), data: [] }]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onStartDateConfirm(e) {
|
|
|
|
|
dateRange.start = formatTimestamp(e.value)
|
|
|
|
|
function onStartDateChange(val) {
|
|
|
|
|
dateRange.start = val
|
|
|
|
|
if (dateRange.start && dateRange.end) {
|
|
|
|
|
if (currentFilter.value === 'product') {
|
|
|
|
|
loadTrendData()
|
|
|
|
|
} else if (currentFilter.value === 'task') {
|
|
|
|
|
loadTaskTrendData()
|
|
|
|
|
}
|
|
|
|
|
loadData()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onEndDateConfirm(e) {
|
|
|
|
|
dateRange.end = formatTimestamp(e.value)
|
|
|
|
|
function onEndDateChange(val) {
|
|
|
|
|
dateRange.end = val
|
|
|
|
|
if (dateRange.start && dateRange.end) {
|
|
|
|
|
if (currentFilter.value === 'product') {
|
|
|
|
|
loadTrendData()
|
|
|
|
|
} else if (currentFilter.value === 'task') {
|
|
|
|
|
loadTaskTrendData()
|
|
|
|
|
}
|
|
|
|
|
loadData()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onPickerClose() {
|
|
|
|
|
if (startPickerRef.value) startPickerRef.value.showByClickInput = false
|
|
|
|
|
if (endPickerRef.value) endPickerRef.value.showByClickInput = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadTrendData() {
|
|
|
|
|
const trendTypeMap = { year: 1, month: 2, week: 3, today: 4, custom: 5 }
|
|
|
|
|
const params = { trendType: trendTypeMap[currentRange.value] || 2 }
|
|
|
|
|
@ -585,21 +560,20 @@ defineExpose({ loadData })
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.u-datetime-picker) {
|
|
|
|
|
:deep(.uni-date) {
|
|
|
|
|
flex: 1;
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
|
|
.u-input {
|
|
|
|
|
padding-left: 40rpx;
|
|
|
|
|
background: transparent;
|
|
|
|
|
.uni-date-editor--x {
|
|
|
|
|
border: none;
|
|
|
|
|
background: transparent;
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.up-icon {
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 12rpx;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
color: #4a90c2;
|
|
|
|
|
.uni-date__x-input {
|
|
|
|
|
background: transparent;
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|