feat(ops): 运维小程序扫码 Tab 新增设备查询与扫码历史子页 + 工作台入口补齐

- 新增 scan/deviceList.vue(设备查询)与 scanHistory.vue(扫码历史)两个子页
- 扫码 Home / sceneMenu 流程调整,api 与 types 补充对应字段
- nav.ts 同步新增两个子页菜单项
- handlers/index 与 scan mock 注册配合调整

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fengpu
2026-07-10 17:07:33 +08:00
co-authored by Claude Opus 4.7
parent e586461af7
commit 0e884b884a
10 changed files with 546 additions and 77 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ import './miniprogram/ops/workbench'
import './miniprogram/ops/stock'
// 实施运维小程序 - 工单(列表 3 段 + 详情 + 接单/关单/维修上报)
import './miniprogram/ops/workOrder'
// 实施运维小程序 - 扫码(识别 / 历史 / 初始化 / 实施记录 / 维修上报 / OTA / 设备监测 5 Tab
// 实施运维小程序 - 扫码(识别 / 历史 / 设备列表 / 初始化 / 实施记录 / 维修上报 / OTA / 设备监测 5 Tab
import './miniprogram/ops/scan'
// 实施运维小程序 - 巡检(计划列表 4 状态 + 任务详情逐项填报 + 异常转工单)
import './miniprogram/ops/inspect'
@@ -2,16 +2,19 @@
* 实施运维小程序 / 扫码 mock
* - 扫码识别设备(按编码查摘要)
* - 扫码历史
* - 设备列表(按客户 / 场所 / 类别 / 编码筛选,扫码兜底入口)
* - 设备初始化 / 实施记录 / 维修上报 / OTA 升级 / 设备监测
*/
import { registerMocks } from '@axios/mockBus'
import type { ApiResponse, RequestParameter } from '@axios'
import type {
DeviceListFilter,
DeviceMonitor,
DeviceSummary,
ImplRecordForm,
InitForm,
ListDeviceItem,
MonitorRepairItem,
MonitorUpdateItem,
OtaTask,
@@ -74,6 +77,20 @@ const deviceMap: Record<string, DeviceSummary> = {
packageName: '—', softwareVersion: '—', lastHeartbeat: '—',
pendingWorkOrders: 0, isIot: true, dataDistributeEnabled: false,
},
'BIKE-NJ-006': {
id: 'dev-103', deviceCode: 'BIKE-NJ-006', deviceName: '6号智能单车(南京玄武)',
category: 'fitness', model: 'BK-Pro',
status: 'activated', siteName: '南京玄武 · 玄武湖健身房', tenantName: '南京健身中心',
packageName: '健身单车App', softwareVersion: 'V1.0.0', lastHeartbeat: '2026-07-03 08:50:00',
pendingWorkOrders: 0, isIot: true, dataDistributeEnabled: false,
},
'SCALE-GZ-008': {
id: 'dev-115', deviceCode: 'SCALE-GZ-008', deviceName: '8号体脂仪(广州天河·已报废)',
category: 'body', model: 'InBody-770',
status: 'scrapped', siteName: '广州天河 · 天河体检站', tenantName: '广州健康管理机构',
packageName: '体脂仪App', softwareVersion: 'V2.9.4', lastHeartbeat: '2026-06-20 16:08:42',
pendingWorkOrders: 0, isIot: false, dataDistributeEnabled: false,
},
}
/** 扫码历史(最近 6 条) */
@@ -149,6 +166,33 @@ const genArcsoftKey = (): string => {
}
registerMocks([
// 设备列表(按客户 / 场所 / 类别 / 编码筛选,用于扫码兜底入口)
{
url: '/mp/ops/scan/device-list',
method: 'GET',
handler: (ctx): ApiResponse<ListDeviceItem[]> => {
const { tenantName, siteName, category, deviceCode } = getParams<DeviceListFilter>(ctx.params)
// 从 deviceMap 派生精简列表项
const list: ListDeviceItem[] = Object.values(deviceMap).map((d) => ({
id: d.id,
deviceCode: d.deviceCode,
deviceName: d.deviceName,
category: d.category,
status: d.status,
siteName: d.siteName,
tenantName: d.tenantName,
model: d.model,
}))
const filtered = list.filter((d) => {
if (tenantName && d.tenantName !== tenantName) return false
if (siteName && d.siteName !== siteName) return false
if (category && d.category !== category) return false
if (deviceCode && !d.deviceCode.toLowerCase().includes(deviceCode.toLowerCase())) return false
return true
})
return ok('查询成功', filtered)
},
},
// 扫码识别设备
{
url: '/mp/ops/scan/scan',
+10
View File
@@ -1084,6 +1084,16 @@ export const miniprograms: NavMiniprogram[] = [
status: 'draft',
},
subPages: [
{
key: 'mp-ops-scan-device-list', label: '设备查询', path: 'device-list',
component: () => import('@pages/miniprogram/ops/scan/deviceList.vue'),
status: 'draft',
},
{
key: 'mp-ops-scan-history', label: '扫码历史', path: 'scan-history',
component: () => import('@pages/miniprogram/ops/scan/scanHistory.vue'),
status: 'draft',
},
{
key: 'mp-ops-scan-scene-menu', label: '场景操作菜单', path: 'scene-menu/:deviceId',
component: () => import('@pages/miniprogram/ops/scan/sceneMenu.vue'),
+44 -54
View File
@@ -10,9 +10,11 @@
<div class="scan-home__line" />
</div>
<div class="scan-home__tip">将设备机身二维码对准框内</div>
<van-button class="scan-home__album" icon="photo-o" plain size="small" @click="onManualInput">
手动输入编码
</van-button>
<!-- 兜底入口:手动输码(精确) + 按条件查找(模糊) -->
<div class="scan-home__actions">
<van-button icon="edit" plain size="small" @click="onManualInput">手动输入编码</van-button>
<van-button icon="search" plain size="small" @click="goDeviceList">按条件查找</van-button>
</div>
</div>
<!-- 模拟扫码按钮原型无真扫码能力 -->
@@ -22,22 +24,18 @@
<van-button type="primary" block @click="onSimScan('CAB-NEW-001')">模拟扫码 · 待激活设备</van-button>
</div>
<!-- 历史扫码 -->
<div class="scan-home__history">
<div class="scan-home__history-title">最近扫码</div>
<van-cell
v-for="item in history"
:key="item.id"
:title="item.deviceName"
:label="`${item.deviceCode} · ${item.scannedAt}`"
is-link
@click="onRescan(item.deviceCode)"
>
<template #right-icon>
<van-tag plain type="primary">{{ item.action }}</van-tag>
</template>
</van-cell>
</div>
<!-- 次级入口:扫码历史抽离到独立页面,首页只保留入口,让取景框更突出 -->
<van-cell
class="scan-home__history-entry"
title="扫码历史"
label="查看最近扫过的设备"
is-link
@click="goScanHistory"
>
<template #icon>
<van-icon name="clock-o" class="scan-home__history-entry-icon" />
</template>
</van-cell>
<!-- 手动输入对话框 -->
<van-dialog
@@ -54,19 +52,17 @@
<script setup lang="ts">
/**
* 实施运维小程序 / 扫码首页
* - 全屏取景框(原型无真扫码提供模拟按钮)
* - 历史扫码列表
* - 手动输入设备编码兜底
* - 全屏取景框(原型无真扫码,提供模拟按钮)
* - 兜底入口:手动输入编码 / 按条件查找设备(跳 deviceList)
* - 扫码历史抽离到 scan-history 子页,首页只保留入口,布局更紧凑
*/
import { onMounted, ref } from 'vue'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { showFailToast } from 'vant'
import { listScanHistory, scanDevice } from './api'
import type { ScanHistoryItem } from './types'
import { scanDevice } from './api'
const router = useRouter()
const history = ref<ScanHistoryItem[]>([])
const inputShow = ref(false)
const inputCode = ref('')
@@ -97,29 +93,19 @@ const onInputConfirm = (): void => {
onSimScan(inputCode.value.trim())
}
const onRescan = (deviceCode: string): void => {
onSimScan(deviceCode)
const goDeviceList = (): void => {
router.push('/miniprogram/ops/device-list')
}
const loadHistory = (): void => {
listScanHistory()
.then((res) => {
if (res.code === '00000') history.value = res.data
})
.catch(() => undefined)
.finally(() => undefined)
const goScanHistory = (): void => {
router.push('/miniprogram/ops/scan-history')
}
onMounted(() => {
loadHistory()
})
</script>
<style scoped lang="less">
.scan-home {
min-height: 100%;
background: #000;
color: #fff;
background: var(--background);
display: flex;
flex-direction: column;
@@ -129,7 +115,7 @@ onMounted(() => {
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px 24px;
padding: 32px 24px 20px;
background: linear-gradient(180deg, #1a1a1a 0%, #000 100%);
}
@@ -174,9 +160,16 @@ onMounted(() => {
color: rgba(255, 255, 255, 0.7);
}
&__album {
// 取景框内两个并列兜底入口
&__actions {
margin-top: 16px;
:deep(.van-button__text) { color: var(--primary); }
display: flex;
gap: 12px;
:deep(.van-button) {
--van-button-plain-text-color: var(--primary);
--van-button-border-color: var(--primary);
}
}
&__sim {
@@ -187,17 +180,14 @@ onMounted(() => {
background: var(--background);
}
&__history {
background: #fff;
padding-bottom: 12px;
}
&__history-entry {
margin-top: 8px;
&__history-title {
padding: 12px 16px 6px;
font-size: var(--font-sm);
font-weight: 600;
color: var(--text-primary);
background: #fff;
&-icon {
font-size: 20px;
color: var(--primary);
margin-right: 8px;
}
}
}
</style>
+8
View File
@@ -3,10 +3,12 @@
*/
import { getRequest, postRequest } from '@axios'
import type {
DeviceListFilter,
DeviceMonitor,
DeviceSummary,
ImplRecordForm,
InitForm,
ListDeviceItem,
OtaTask,
OtaVersion,
RepairForm,
@@ -24,6 +26,12 @@ export const scanDevice = (
export const listScanHistory = (): Promise<{ code: string; msg: string; data: ScanHistoryItem[] }> =>
getRequest('axiosRequest', '/mp/ops/scan/history')
/** 设备列表(按客户 / 场所 / 类别 / 编码筛选,用于扫码兜底场景) */
export const listDevices = (
filters: DeviceListFilter,
): Promise<{ code: string; msg: string; data: ListDeviceItem[] }> =>
getRequest('axiosRequest', '/mp/ops/scan/device-list', filters)
/** 提交设备初始化 */
export const submitInit = (
params: InitForm,
@@ -0,0 +1,284 @@
<template>
<div class="device-list">
<van-nav-bar title="设备查询" left-arrow @click-left="onBack" />
<!-- 筛选条:客户 / 场所 / 类别 三下拉 + 编码模糊搜索 -->
<div class="device-list__filter">
<van-dropdown-menu>
<van-dropdown-item v-model="filter.tenantName" :options="tenantOpts" @change="onTenantChange" />
<van-dropdown-item v-model="filter.siteName" :options="siteOpts" @change="onFilterChange" />
<van-dropdown-item v-model="filter.category" :options="categoryOpts" @change="onFilterChange" />
</van-dropdown-menu>
<van-search
v-model="filter.deviceCode"
placeholder="搜索设备编码"
shape="round"
@search="onFilterChange"
/>
</div>
<!-- 结果计数 -->
<div class="device-list__count"> {{ filtered.length }} 台设备</div>
<!-- 加载骨架 -->
<div v-if="loading" class="device-list__skeleton">
<van-skeleton v-for="n in 3" :key="n" title :row="2" />
</div>
<!-- 空态 -->
<van-empty
v-else-if="!filtered.length"
image="search"
description="暂无符合条件的设备"
/>
<!-- 设备卡片列表 -->
<div v-else class="device-list__cards">
<div
v-for="d in filtered"
:key="d.id"
class="dev-card"
@click="onPick(d.deviceCode)"
>
<div class="dev-card__header">
<van-icon name="desktop-o" class="dev-card__icon" />
<div class="dev-card__main">
<div class="dev-card__name">{{ d.deviceName }}</div>
<div class="dev-card__code">{{ d.deviceCode }}</div>
</div>
<van-tag :type="statusTagType(d.status)">{{ statusText(d.status) }}</van-tag>
</div>
<div class="dev-card__meta">
<div><span>型号</span>{{ d.model }}</div>
<div><span>类别</span>{{ categoryText(d.category) }}</div>
<div><span>客户</span>{{ d.tenantName }}</div>
<div><span>场所</span>{{ d.siteName }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
/**
* 实施运维小程序 / 设备查询
* - 扫码兜底入口:不在现场 / 二维码损坏时,按客户 / 场所 / 类别 / 编码快速定位设备
* - 选中设备 → 跳场景操作菜单 sceneMenu(deviceCode),复用 9 项场景操作(零业务重复)
* - 原型数据量小(运维工程师负责的设备有限),首次拉全集 + 本地 distinct 选项 + 本地过滤
* 研发接手时 filters 参数契约保留,后端如需服务端筛选可直接启用
*/
import { computed, onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { showFailToast } from 'vant'
import { listDevices } from './api'
import type { DeviceCategory, DeviceStatus, ListDeviceItem } from './types'
const router = useRouter()
const all = ref<ListDeviceItem[]>([])
const loading = ref(false)
const filter = reactive<{
tenantName: string
siteName: string
category: DeviceCategory | ''
deviceCode: string
}>({
tenantName: '',
siteName: '',
category: '',
deviceCode: '',
})
/** 客户下拉(从全集 distinct) */
const tenantOpts = computed(() => [
{ text: '全部客户', value: '' },
...Array.from(new Set(all.value.map((d) => d.tenantName))).map((n) => ({ text: n, value: n })),
])
/** 场所下拉(随客户联动:选中客户时只展示该客户下的场所) */
const siteOpts = computed(() => {
const scoped = filter.tenantName
? all.value.filter((d) => d.tenantName === filter.tenantName)
: all.value
return [
{ text: '全部场所', value: '' },
...Array.from(new Set(scoped.map((d) => d.siteName))).map((n) => ({ text: n, value: n })),
]
})
/** 设备类别下拉(固定 5 大顶级分类,与 DeviceCategory 对齐) */
const categoryOpts: Array<{ text: string; value: DeviceCategory | '' }> = [
{ text: '全部类别', value: '' },
{ text: '智养餐饮设备', value: 'catering' },
{ text: '体重测量设备', value: 'body' },
{ text: '智能穿戴设备', value: 'wearable' },
{ text: '健身器材设备', value: 'fitness' },
{ text: '健康监测设备', value: 'health' },
]
/** 本地过滤(原型数据量小,直接内存过滤避免每次切下拉都发请求) */
const filtered = computed<ListDeviceItem[]>(() => {
const kw = filter.deviceCode.trim().toLowerCase()
return all.value.filter((d) => {
if (filter.tenantName && d.tenantName !== filter.tenantName) return false
if (filter.siteName && d.siteName !== filter.siteName) return false
if (filter.category && d.category !== filter.category) return false
if (kw && !d.deviceCode.toLowerCase().includes(kw)) return false
return true
})
})
const statusText = (s: DeviceStatus): string => ({
inactive: '未激活',
activated: '已激活',
inUse: '在用',
scrapped: '已报废',
})[s]
const statusTagType = (s: DeviceStatus): 'primary' | 'warning' | 'success' | 'default' =>
s === 'inUse' ? 'success' : s === 'activated' ? 'primary' : s === 'inactive' ? 'warning' : 'default'
const categoryText = (c: DeviceCategory): string => ({
catering: '智养餐饮',
body: '体重测量',
wearable: '智能穿戴',
fitness: '健身器材',
health: '健康监测',
})[c]
/** 切客户后联动:已选场所若不在新客户范围内,清空 */
const onTenantChange = (): void => {
if (filter.siteName && !siteOpts.value.some((o) => o.value === filter.siteName)) {
filter.siteName = ''
}
}
/** 编码 search 触发(本地过滤,computed 自动响应,这里仅占位避免 lint) */
const onFilterChange = (): void => undefined
const onPick = (deviceCode: string): void => {
router.push(`/miniprogram/ops/scene-menu/${deviceCode}`)
}
const onBack = (): void => {
router.back()
}
const loadAll = (): void => {
loading.value = true
listDevices({})
.then((res) => {
if (res.code === '00000') all.value = res.data
else showFailToast(res.msg)
})
.catch(() => undefined)
.finally(() => {
loading.value = false
})
}
onMounted(() => {
loadAll()
})
</script>
<style scoped lang="less">
.device-list {
min-height: 100%;
background: var(--background);
padding-bottom: 16px;
&__filter {
background: #fff;
box-shadow: var(--shadow-sm);
}
&__count {
padding: 8px 16px;
font-size: var(--font-xs);
color: var(--text-secondary);
}
&__skeleton {
padding: 12px 16px;
background: #fff;
.van-skeleton {
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
&:last-child {
border-bottom: none;
margin-bottom: 0;
}
}
}
&__cards {
padding: 0 12px;
display: flex;
flex-direction: column;
gap: 8px;
}
}
.dev-card {
background: #fff;
border-radius: var(--radius-md);
padding: 12px;
box-shadow: var(--shadow-sm);
transition: transform 0.15s ease;
&:active {
transform: scale(0.99);
}
&__header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
&__icon {
font-size: 24px;
color: var(--primary);
}
&__main {
flex: 1;
min-width: 0;
}
&__name {
font-size: var(--font-base);
font-weight: 600;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__code {
font-family: monospace;
font-size: var(--font-xs);
color: var(--text-secondary);
margin-top: 2px;
}
&__meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px 12px;
font-size: var(--font-xs);
color: var(--text-secondary);
span {
color: var(--text-placeholder);
margin-right: 6px;
}
}
}
</style>
@@ -0,0 +1,70 @@
<template>
<div class="scan-history">
<van-nav-bar title="扫码历史" left-arrow @click-left="onBack" />
<van-empty v-if="!list.length" image="search" description="暂无扫码历史" />
<van-cell-group v-else inset class="scan-history__list">
<van-cell
v-for="item in list"
:key="item.id"
:title="item.deviceName"
:label="`${item.deviceCode} · ${item.scannedAt}`"
is-link
@click="onPick(item.deviceCode)"
>
<template #right-icon>
<van-tag plain type="primary">{{ item.action }}</van-tag>
</template>
</van-cell>
</van-cell-group>
</div>
</template>
<script setup lang="ts">
/**
* 实施运维小程序 / 扫码历史
* - 从 scan/Home.vue 抽离,首页只放入口链接,让取景框更突出
* - 点击历史项 → 跳 sceneMenu(deviceCode),等价于重新扫码
*/
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { listScanHistory } from './api'
import type { ScanHistoryItem } from './types'
const router = useRouter()
const list = ref<ScanHistoryItem[]>([])
const onPick = (deviceCode: string): void => {
router.push(`/miniprogram/ops/scene-menu/${deviceCode}`)
}
const onBack = (): void => {
router.back()
}
const loadList = (): void => {
listScanHistory()
.then((res) => {
if (res.code === '00000') list.value = res.data
})
.catch(() => undefined)
.finally(() => undefined)
}
onMounted(() => {
loadList()
})
</script>
<style scoped lang="less">
.scan-history {
min-height: 100%;
background: var(--background);
padding-top: 8px;
&__list {
margin-top: 8px;
}
}
</style>
+1 -22
View File
@@ -58,7 +58,7 @@
/**
* 实施运维小程序 / 扫码场景操作菜单
* - 设备摘要卡(编码 / 型号 / 状态 / 待处理工单红点)
* - 9 项操作按设备状态过滤显示(6 项运维 + 3 项库存作业)
* - 6运维操作按设备状态过滤显示(库存作业请走工作台「库存作业」入口
*/
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
@@ -77,8 +77,6 @@ interface SceneAction {
icon: string
path: (deviceId: string) => string
statuses: DeviceStatus[]
/** 库存相关场景独立于设备生命周期,可与任意状态并存 */
isStock?: boolean
}
const ALL_ACTIONS: SceneAction[] = [
@@ -112,25 +110,6 @@ const ALL_ACTIONS: SceneAction[] = [
icon: 'eye-o', path: (id) => `/miniprogram/ops/monitor/${id}`,
statuses: ['activated', 'inUse', 'scrapped'],
},
// 库存作业 3 个场景(扫码进入时自动带入 deviceCode
{
key: 'stock-inbound', label: '扫码入库', desc: '将本设备登记到收货入库清单',
icon: 'after-sale', path: (code) => `/miniprogram/ops/stock-inbound?deviceCode=${code}`,
statuses: ['inactive', 'activated', 'inUse', 'scrapped'],
isStock: true,
},
{
key: 'stock-outbound', label: '扫码出库', desc: '将本设备加入装机出库清单',
icon: 'logistics', path: (code) => `/miniprogram/ops/stock-outbound?deviceCode=${code}`,
statuses: ['inactive', 'activated', 'inUse', 'scrapped'],
isStock: true,
},
{
key: 'stock-inventory', label: '扫码盘点', desc: '在当前盘点单中标记本设备实盘结果',
icon: 'balance-list-o', path: (code) => `/miniprogram/ops/stock-inventory?deviceCode=${code}&id=inv-006`,
statuses: ['inactive', 'activated', 'inUse', 'scrapped'],
isStock: true,
},
]
const availableActions = computed<SceneAction[]>(() => {
+24
View File
@@ -167,3 +167,27 @@ export interface DeviceMonitor {
repairs: MonitorRepairItem[]
updates: MonitorUpdateItem[]
}
/** 设备列表项(查询场景精简版,剔除心跳 / 工单 / IOT 等监测字段,仅用于列表展示) */
export interface ListDeviceItem {
id: string
deviceCode: string
deviceName: string
category: DeviceCategory
status: DeviceStatus
siteName: string
tenantName: string
model: string
}
/** 设备列表筛选条件(用于"按客户 / 场所 / 类别 / 编码"快速定位设备) */
export interface DeviceListFilter {
/** 客户(租户)名,空表示不限 */
tenantName?: string
/** 场所名,空表示不限 */
siteName?: string
/** 设备顶级分类,空表示不限 */
category?: DeviceCategory
/** 设备编码模糊匹配 */
deviceCode?: string
}
@@ -103,6 +103,19 @@
</div>
</div>
</div>
<!-- 设备查询区扫码兜底入口不在现场 / 二维码损坏时按条件定位设备 -->
<div class="ops-workbench__section">
<div class="ops-workbench__section-title">设备查询</div>
<div class="dev-search-card" @click="goDeviceList">
<van-icon name="search" class="dev-search-card__icon" />
<div class="dev-search-card__main">
<div class="dev-search-card__label">按客户 / 场所 / 类别 / 编码查找设备</div>
<div class="dev-search-card__sub">不在现场或二维码损坏时从列表快速定位</div>
</div>
<van-icon name="arrow" class="dev-search-card__arrow" />
</div>
</div>
</div>
</template>
@@ -141,6 +154,10 @@ const goPath = (path: string): void => {
router.push(path)
}
const goDeviceList = (): void => {
router.push('/miniprogram/ops/device-list')
}
const goAlarmList = (): void => {
router.push('/miniprogram/ops/alarm-list')
}
@@ -454,4 +471,47 @@ onMounted(() => {
.quick-entry__icon { color: #595959; }
}
}
.dev-search-card {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
background: #fff;
border-radius: var(--radius-md);
border-left: 3px solid var(--primary);
box-shadow: var(--shadow-sm);
transition: transform 0.15s ease;
&:active {
transform: scale(0.99);
}
&__icon {
font-size: 24px;
color: var(--primary);
}
&__main {
flex: 1;
min-width: 0;
}
&__label {
font-size: var(--font-base);
font-weight: 600;
color: var(--text-primary);
}
&__sub {
margin-top: 2px;
font-size: var(--font-xs);
color: var(--text-secondary);
}
&__arrow {
font-size: 16px;
color: var(--text-placeholder);
}
}
</style>