fix(dashboard): correct overview data and states
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
package dashboard
|
package dashboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNamedStatusesUsesFallbackForUnknownStatus(t *testing.T) {
|
func TestNamedStatusesUsesFallbackForUnknownStatus(t *testing.T) {
|
||||||
@@ -12,6 +15,24 @@ func TestNamedStatusesUsesFallbackForUnknownStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadPaymentChannelsUsesPaymentOrderChannelColumn(t *testing.T) {
|
||||||
|
_, mock := setupDashboardDatabase(t)
|
||||||
|
mock.ExpectQuery(regexp.QuoteMeta(
|
||||||
|
`SELECT channel AS name, COALESCE(SUM(amount), 0) AS value FROM "payment_order" WHERE (status = $1 AND payment_status = $2) AND "payment_order"."deleted_at" IS NULL GROUP BY "channel" ORDER BY value DESC`,
|
||||||
|
)).WithArgs(1, 23).WillReturnRows(
|
||||||
|
sqlmock.NewRows([]string{"name", "value"}).AddRow("wechat", int64(1200)),
|
||||||
|
)
|
||||||
|
|
||||||
|
var result []MetricSlice
|
||||||
|
if err := loadPaymentChannels(&result); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(result) != 1 || result[0].Name != "wechat" || result[0].Value != 1200 {
|
||||||
|
t.Fatalf("payment channels = %#v", result)
|
||||||
|
}
|
||||||
|
assertDashboardMockExpectations(t, mock)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFillRecentDaysIncludesMissingDays(t *testing.T) {
|
func TestFillRecentDaysIncludesMissingDays(t *testing.T) {
|
||||||
now := time.Date(2026, 7, 29, 12, 0, 0, 0, time.Local)
|
now := time.Date(2026, 7, 29, 12, 0, 0, 0, time.Local)
|
||||||
got := fillRecentDays([]dailyAggregate{{
|
got := fillRecentDays([]dailyAggregate{{
|
||||||
|
|||||||
@@ -126,10 +126,7 @@ func GetDashboardOverview() (DashboardStatistics, error) {
|
|||||||
}
|
}
|
||||||
result.ProductStatuses = namedStatuses(productStatuses, productStatusNames)
|
result.ProductStatuses = namedStatuses(productStatuses, productStatusNames)
|
||||||
|
|
||||||
if err := impl.DBService.Model(&models.PaymentOrder{}).
|
if err := loadPaymentChannels(&result.PaymentChannels); err != nil {
|
||||||
Select("pay_channel AS name, COALESCE(SUM(amount), 0) AS value").
|
|
||||||
Where("status = ? AND payment_status = ?", common.StatusEnable, 23).
|
|
||||||
Group("pay_channel").Order("value DESC").Scan(&result.PaymentChannels).Error; err != nil {
|
|
||||||
return DashboardStatistics{}, err
|
return DashboardStatistics{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +141,13 @@ func GetDashboardOverview() (DashboardStatistics, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadPaymentChannels(result *[]MetricSlice) error {
|
||||||
|
return impl.DBService.Model(&models.PaymentOrder{}).
|
||||||
|
Select("channel AS name, COALESCE(SUM(amount), 0) AS value").
|
||||||
|
Where("status = ? AND payment_status = ?", common.StatusEnable, 23).
|
||||||
|
Group("channel").Order("value DESC").Scan(result).Error
|
||||||
|
}
|
||||||
|
|
||||||
func namedStatuses(rows []statusAggregate, names map[int]string) []MetricSlice {
|
func namedStatuses(rows []statusAggregate, names map[int]string) []MetricSlice {
|
||||||
result := make([]MetricSlice, 0, len(rows))
|
result := make([]MetricSlice, 0, len(rows))
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { compile, computed, defineComponent, h, ref } from 'vue';
|
import { compile, computed, defineComponent, h, ref, watch } from 'vue';
|
||||||
import type { RouteMeta } from 'vue-router';
|
import type { RouteMeta } from 'vue-router';
|
||||||
import { type RouteRecordRaw, useRoute, useRouter } from 'vue-router';
|
import { type RouteRecordRaw, useRoute, useRouter } from 'vue-router';
|
||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
@@ -67,21 +67,24 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
listenerRouteChange((newRoute) => {
|
const syncMenuSelection = (newRoute: typeof route) => {
|
||||||
const { requiresAuth, activeMenu, hideInMenu } = newRoute.meta;
|
const { requiresAuth, activeMenu, hideInMenu } = newRoute.meta;
|
||||||
if (requiresAuth && (!hideInMenu || activeMenu)) {
|
if (requiresAuth && (!hideInMenu || activeMenu)) {
|
||||||
|
const target = (activeMenu || newRoute.name) as string;
|
||||||
const menuOpenKeys = findMenuOpenKeys(
|
const menuOpenKeys = findMenuOpenKeys(
|
||||||
(activeMenu || newRoute.name) as string,
|
target,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!menuOpenKeys.length) return;
|
||||||
|
|
||||||
const keySet = new Set([...menuOpenKeys, ...openKeys.value]);
|
const keySet = new Set([...menuOpenKeys, ...openKeys.value]);
|
||||||
openKeys.value = [...keySet];
|
openKeys.value = [...keySet];
|
||||||
|
|
||||||
selectedKey.value = [
|
selectedKey.value = [target];
|
||||||
activeMenu || menuOpenKeys[menuOpenKeys.length - 1],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}, true);
|
};
|
||||||
|
listenerRouteChange(syncMenuSelection, true);
|
||||||
|
watch(menuTree, () => syncMenuSelection(route), { deep: true, flush: 'post' });
|
||||||
const setCollapse = (val: boolean) => {
|
const setCollapse = (val: boolean) => {
|
||||||
if (appStore.device === 'desktop')
|
if (appStore.device === 'desktop')
|
||||||
appStore.updateSettings({ menuCollapse: val });
|
appStore.updateSettings({ menuCollapse: val });
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
<template #action><a-button size="small" @click="loadOverview">重新加载</a-button></template>
|
<template #action><a-button size="small" @click="loadOverview">重新加载</a-button></template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
<div class="dashboard-meta">当前配送点业务汇总 · 更新时间:{{ updatedAt || '尚未加载' }}</div>
|
<div class="dashboard-meta">当前配送点业务汇总 · 更新时间:{{ updatedAtLabel }}</div>
|
||||||
<a-grid :cols="{ xs: 1, sm: 2, lg: 4 }" :col-gap="16" :row-gap="16">
|
<a-grid :cols="{ xs: 1, sm: 2, lg: 4 }" :col-gap="16" :row-gap="16">
|
||||||
<a-grid-item v-for="item in cards" :key="item.key">
|
<a-grid-item v-for="item in cards" :key="item.key">
|
||||||
<a-card :bordered="false" class="metric-card">
|
<a-card :bordered="false" class="metric-card">
|
||||||
@@ -17,12 +17,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { computed, onMounted, reactive, ref } from 'vue';
|
||||||
import { request } from '@/api/http';
|
import { request } from '@/api/http';
|
||||||
type Overview = Record<'staff_count' | 'user_count' | 'contract_count' | 'order_count', number>;
|
type Overview = Record<'staff_count' | 'user_count' | 'contract_count' | 'order_count', number>;
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const errorMessage = ref('');
|
const errorMessage = ref('');
|
||||||
const updatedAt = ref('');
|
const updatedAt = ref('');
|
||||||
|
const updatedAtLabel = computed(() => updatedAt.value || (errorMessage.value ? '加载失败' : '加载中…'));
|
||||||
const overview = reactive<Overview>({ staff_count: 0, user_count: 0, contract_count: 0, order_count: 0 });
|
const overview = reactive<Overview>({ staff_count: 0, user_count: 0, contract_count: 0, order_count: 0 });
|
||||||
const cards: Array<{ key: keyof Overview; label: string; hint: string }> = [
|
const cards: Array<{ key: keyof Overview; label: string; hint: string }> = [
|
||||||
{ key: 'staff_count', label: '配送人员', hint: '本站关联配送人员' }, { key: 'user_count', label: '服务用户', hint: '本站有效服务关系' },
|
{ key: 'staff_count', label: '配送人员', hint: '本站关联配送人员' }, { key: 'user_count', label: '服务用户', hint: '本站有效服务关系' },
|
||||||
@@ -44,6 +45,6 @@ onMounted(loadOverview);
|
|||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.dashboard-alert { margin-bottom: 16px; }
|
.dashboard-alert { margin-bottom: 16px; }
|
||||||
.dashboard-meta { margin-bottom: 12px; color: var(--color-text-3); font-size: 12px; text-align: right; }
|
.dashboard-meta { margin-bottom: 12px; color: var(--color-text-3); font-size: 12px; text-align: right; }
|
||||||
.metric-card { min-height: 112px; }
|
.metric-card { min-height: 132px; }
|
||||||
.metric-hint { margin-top: 8px; color: var(--color-text-3); font-size: 12px; }
|
.metric-hint { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--color-border-2); color: var(--color-text-2); font-size: 13px; line-height: 20px; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { compile, computed, defineComponent, h, ref } from 'vue';
|
import { compile, computed, defineComponent, h, ref, watch } from 'vue';
|
||||||
import type { RouteMeta } from 'vue-router';
|
import type { RouteMeta } from 'vue-router';
|
||||||
import { type RouteRecordRaw, useRoute, useRouter } from 'vue-router';
|
import { type RouteRecordRaw, useRoute, useRouter } from 'vue-router';
|
||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
@@ -67,21 +67,24 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
listenerRouteChange((newRoute) => {
|
const syncMenuSelection = (newRoute: typeof route) => {
|
||||||
const { requiresAuth, activeMenu, hideInMenu } = newRoute.meta;
|
const { requiresAuth, activeMenu, hideInMenu } = newRoute.meta;
|
||||||
if (requiresAuth && (!hideInMenu || activeMenu)) {
|
if (requiresAuth && (!hideInMenu || activeMenu)) {
|
||||||
|
const target = (activeMenu || newRoute.name) as string;
|
||||||
const menuOpenKeys = findMenuOpenKeys(
|
const menuOpenKeys = findMenuOpenKeys(
|
||||||
(activeMenu || newRoute.name) as string,
|
target,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!menuOpenKeys.length) return;
|
||||||
|
|
||||||
const keySet = new Set([...menuOpenKeys, ...openKeys.value]);
|
const keySet = new Set([...menuOpenKeys, ...openKeys.value]);
|
||||||
openKeys.value = [...keySet];
|
openKeys.value = [...keySet];
|
||||||
|
|
||||||
selectedKey.value = [
|
selectedKey.value = [target];
|
||||||
activeMenu || menuOpenKeys[menuOpenKeys.length - 1],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}, true);
|
};
|
||||||
|
listenerRouteChange(syncMenuSelection, true);
|
||||||
|
watch(menuTree, () => syncMenuSelection(route), { deep: true, flush: 'post' });
|
||||||
const setCollapse = (val: boolean) => {
|
const setCollapse = (val: boolean) => {
|
||||||
if (appStore.device === 'desktop')
|
if (appStore.device === 'desktop')
|
||||||
appStore.updateSettings({ menuCollapse: val });
|
appStore.updateSettings({ menuCollapse: val });
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
<template #action><a-button size="small" @click="loadOverview">重新加载</a-button></template>
|
<template #action><a-button size="small" @click="loadOverview">重新加载</a-button></template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
<div class="dashboard-meta">当前气站业务汇总 · 更新时间:{{ updatedAt || '尚未加载' }}</div>
|
<div class="dashboard-meta">当前气站业务汇总 · 更新时间:{{ updatedAtLabel }}</div>
|
||||||
<a-grid :cols="{ xs: 1, sm: 2, lg: 5 }" :col-gap="16" :row-gap="16">
|
<a-grid :cols="{ xs: 1, sm: 2, lg: 5 }" :col-gap="16" :row-gap="16">
|
||||||
<a-grid-item v-for="item in cards" :key="item.key">
|
<a-grid-item v-for="item in cards" :key="item.key">
|
||||||
<a-card :bordered="false" class="metric-card">
|
<a-card :bordered="false" class="metric-card">
|
||||||
@@ -18,13 +18,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { computed, onMounted, reactive, ref } from 'vue';
|
||||||
import { request } from '@/api/http';
|
import { request } from '@/api/http';
|
||||||
|
|
||||||
type Overview = Record<'delivery_count' | 'staff_count' | 'user_count' | 'contract_count' | 'order_count', number>;
|
type Overview = Record<'delivery_count' | 'staff_count' | 'user_count' | 'contract_count' | 'order_count', number>;
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const errorMessage = ref('');
|
const errorMessage = ref('');
|
||||||
const updatedAt = ref('');
|
const updatedAt = ref('');
|
||||||
|
const updatedAtLabel = computed(() => updatedAt.value || (errorMessage.value ? '加载失败' : '加载中…'));
|
||||||
const overview = reactive<Overview>({ delivery_count: 0, staff_count: 0, user_count: 0, contract_count: 0, order_count: 0 });
|
const overview = reactive<Overview>({ delivery_count: 0, staff_count: 0, user_count: 0, contract_count: 0, order_count: 0 });
|
||||||
const cards: Array<{ key: keyof Overview; label: string; hint: string }> = [
|
const cards: Array<{ key: keyof Overview; label: string; hint: string }> = [
|
||||||
{ key: 'delivery_count', label: '配送点', hint: '当前气站服务范围' },
|
{ key: 'delivery_count', label: '配送点', hint: '当前气站服务范围' },
|
||||||
@@ -52,6 +53,6 @@ onMounted(loadOverview);
|
|||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.dashboard-alert { margin-bottom: 16px; }
|
.dashboard-alert { margin-bottom: 16px; }
|
||||||
.dashboard-meta { margin-bottom: 12px; color: var(--color-text-3); font-size: 12px; text-align: right; }
|
.dashboard-meta { margin-bottom: 12px; color: var(--color-text-3); font-size: 12px; text-align: right; }
|
||||||
.metric-card { min-height: 112px; }
|
.metric-card { min-height: 132px; }
|
||||||
.metric-hint { margin-top: 8px; color: var(--color-text-3); font-size: 12px; }
|
.metric-hint { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--color-border-2); color: var(--color-text-2); font-size: 13px; line-height: 20px; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { compile, computed, defineComponent, h, ref } from 'vue';
|
import { compile, computed, defineComponent, h, ref, watch } from 'vue';
|
||||||
import type { RouteMeta } from 'vue-router';
|
import type { RouteMeta } from 'vue-router';
|
||||||
import { type RouteRecordRaw, useRoute, useRouter } from 'vue-router';
|
import { type RouteRecordRaw, useRoute, useRouter } from 'vue-router';
|
||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
@@ -67,21 +67,24 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
listenerRouteChange((newRoute) => {
|
const syncMenuSelection = (newRoute: typeof route) => {
|
||||||
const { requiresAuth, activeMenu, hideInMenu } = newRoute.meta;
|
const { requiresAuth, activeMenu, hideInMenu } = newRoute.meta;
|
||||||
if (requiresAuth && (!hideInMenu || activeMenu)) {
|
if (requiresAuth && (!hideInMenu || activeMenu)) {
|
||||||
|
const target = (activeMenu || newRoute.name) as string;
|
||||||
const menuOpenKeys = findMenuOpenKeys(
|
const menuOpenKeys = findMenuOpenKeys(
|
||||||
(activeMenu || newRoute.name) as string,
|
target,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!menuOpenKeys.length) return;
|
||||||
|
|
||||||
const keySet = new Set([...menuOpenKeys, ...openKeys.value]);
|
const keySet = new Set([...menuOpenKeys, ...openKeys.value]);
|
||||||
openKeys.value = [...keySet];
|
openKeys.value = [...keySet];
|
||||||
|
|
||||||
selectedKey.value = [
|
selectedKey.value = [target];
|
||||||
activeMenu || menuOpenKeys[menuOpenKeys.length - 1],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}, true);
|
};
|
||||||
|
listenerRouteChange(syncMenuSelection, true);
|
||||||
|
watch(menuTree, () => syncMenuSelection(route), { deep: true, flush: 'post' });
|
||||||
const setCollapse = (val: boolean) => {
|
const setCollapse = (val: boolean) => {
|
||||||
if (appStore.device === 'desktop')
|
if (appStore.device === 'desktop')
|
||||||
appStore.updateSettings({ menuCollapse: val });
|
appStore.updateSettings({ menuCollapse: val });
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
<template #action><a-button size="small" @click="loadOverview">重新加载</a-button></template>
|
<template #action><a-button size="small" @click="loadOverview">重新加载</a-button></template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
<div class="dashboard-meta">统计口径以服务端为准 · 更新时间:{{ updatedAt || '尚未加载' }}</div>
|
<div class="dashboard-meta">统计口径以服务端为准 · 更新时间:{{ updatedAtLabel }}</div>
|
||||||
<a-grid :cols="{ xs: 1, sm: 2, lg: 4 }" :col-gap="16" :row-gap="16">
|
<a-grid :cols="{ xs: 1, sm: 2, lg: 4 }" :col-gap="16" :row-gap="16">
|
||||||
<a-grid-item v-for="item in cards" :key="item.key">
|
<a-grid-item v-for="item in cards" :key="item.key">
|
||||||
<a-card :bordered="false" class="metric-card">
|
<a-card :bordered="false" class="metric-card">
|
||||||
@@ -65,16 +65,15 @@ const emptyOverview = (): DashboardOverview => ({
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const errorMessage = ref('');
|
const errorMessage = ref('');
|
||||||
const updatedAt = ref('');
|
const updatedAt = ref('');
|
||||||
|
const updatedAtLabel = computed(() => updatedAt.value || (errorMessage.value ? '加载失败' : '加载中…'));
|
||||||
const overview = ref<DashboardOverview>(emptyOverview());
|
const overview = ref<DashboardOverview>(emptyOverview());
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const cards: { key: CountKey; label: string; hint: string; money?: boolean }[] = [
|
const cards: { key: CountKey; label: string; hint: string; money?: boolean }[] = [
|
||||||
{ key: 'gas_basic_count', label: '启用气站', hint: '当前正常运营' },
|
{ key: 'gas_basic_count', label: '气站总数', hint: '正常运营气站总数' },
|
||||||
{ key: 'delivery_basic_count', label: '启用配送点', hint: '跨组织汇总' },
|
{ key: 'delivery_basic_count', label: '配送点总数', hint: '正常运营配送点总数' },
|
||||||
{ key: 'staff_count', label: '在岗人员', hint: '当前可接单' },
|
{ key: 'user_count', label: '客户总数', hint: '有效客户账户总数' },
|
||||||
{ key: 'user_count', label: '启用客户', hint: '有效业主账户' },
|
{ key: 'product_count', label: '智能气阀', hint: '平台智能气阀总数' },
|
||||||
{ key: 'product_count', label: '智能气阀', hint: '已启用资产' },
|
|
||||||
{ key: 'active_contract_count', label: '生效合同', hint: '当前履约中' },
|
|
||||||
{ key: 'today_order_count', label: '今日订单', hint: '自然日新增' },
|
{ key: 'today_order_count', label: '今日订单', hint: '自然日新增' },
|
||||||
{ key: 'today_order_amount', label: '今日应付金额', hint: '订单口径', money: true },
|
{ key: 'today_order_amount', label: '今日应付金额', hint: '订单口径', money: true },
|
||||||
{ key: 'pending_ticket_count', label: '待受理工单', hint: '客服待办' },
|
{ key: 'pending_ticket_count', label: '待受理工单', hint: '客服待办' },
|
||||||
@@ -149,8 +148,8 @@ onMounted(loadOverview);
|
|||||||
.dashboard-spin { width: 100%; }
|
.dashboard-spin { width: 100%; }
|
||||||
.dashboard-alert { margin-bottom: 16px; }
|
.dashboard-alert { margin-bottom: 16px; }
|
||||||
.dashboard-meta { margin-bottom: 12px; color: var(--color-text-3); font-size: 12px; text-align: right; }
|
.dashboard-meta { margin-bottom: 12px; color: var(--color-text-3); font-size: 12px; text-align: right; }
|
||||||
.metric-card { min-height: 120px; }
|
.metric-card { min-height: 132px; }
|
||||||
.metric-hint { margin-top: 8px; color: var(--color-text-3); font-size: 12px; }
|
.metric-hint { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--color-border-2); color: var(--color-text-2); font-size: 13px; line-height: 20px; }
|
||||||
.section-card { margin-top: 16px; }
|
.section-card { margin-top: 16px; }
|
||||||
.quick-action { height: 52px; justify-content: flex-start; padding: 0 18px; }
|
.quick-action { height: 52px; justify-content: flex-start; padding: 0 18px; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user