Files
device-voice-h5/components/FunctionCard.vue
huang c341ef1aac feat: 重构移动端 H5 页面视觉设计
基于移动优先原则优化布局、排版、颜色和间距,提升设计感和专业度。

- 重构全局设计系统:颜色、阴影、间距、圆角、排版
- 优化 UserInfoCard:更大的头像和状态徽章设计
- 优化 DeviceStatusCard:信息层级和指标网格
- 优化 TrafficCard:进度条和数字排版
- 优化 FunctionCard:4列布局和触摸反馈
- 优化 WifiCard:列表设计和信息层次
- 优化 FloatingButton:去除渐变,纯色设计

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 18:18:25 +08:00

138 lines
4.0 KiB
Vue

<template>
<view class="card function-card">
<view class="card-header mb-5">
<text class="text-heading">功能服务</text>
</view>
<view class="function-grid">
<view
v-for="item in displayFunctions"
:key="item.key"
class="function-item interactive"
:class="{ 'is-disabled': item.disabled }"
@click="handleItemClick(item)"
role="button"
tabindex="0"
>
<view class="function-icon-wrap">
<image class="function-icon" :src="item.icon" mode="aspectFit" :alt="item.name"></image>
</view>
<text class="function-name" :class="{ 'text-primary-color': item.highlight }">{{ item.name }}</text>
</view>
</view>
<slot></slot>
</view>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
realNameStatus: {
type: String,
default: ''
},
alreadyBindPhone: {
type: Boolean,
default: false
},
isDevice: {
type: Boolean,
default: true
}
});
const emit = defineEmits(['enter', 'sync']);
const functions = computed(() => {
const list = [
{ key: 'authentication', name: props.realNameStatus || '实名认证', icon: '/static/authentication.png', action: 'authentication', highlight: props.realNameStatus === '已实名' },
{ key: 'sync', name: '运营数据同步', icon: '/static/data.png', action: 'sync' },
{ key: 'package-order', name: '套餐订购', icon: '/static/shop.png', action: 'package-order' },
{ key: 'order-list', name: '我的订单', icon: '/static/order.png', action: 'order-list' },
{ key: 'back', name: '后台管理', icon: '/static/back.png', action: 'back', deviceOnly: true },
{ key: 'switch', name: '切换运营商', icon: '/static/change.png', action: 'switch', deviceOnly: true },
{ key: 'asset-package', name: '资产套餐历史', icon: '/static/shop.png', action: 'asset-package' },
{ key: 'bind', name: props.alreadyBindPhone ? '已绑定' : '绑定手机号', icon: '/static/bind-phone.png', action: 'bind', highlight: props.alreadyBindPhone, disabled: props.alreadyBindPhone },
{ key: 'change-phone', name: '更换手机号', icon: '/static/change-phone.png', action: 'change-phone' },
{ key: 'device-exchange', name: '设备换货', icon: '/static/change.png', action: 'device-exchange', deviceOnly: true },
{ key: 'wallet', name: '我的钱包', icon: '/static/wallet.png', action: 'wallet' },
{ key: 'recover', name: '重启设备', icon: '/static/recover.png', action: 'recover', deviceOnly: true },
{ key: 'restart', name: '恢复出厂', icon: '/static/restart.png', action: 'restart', deviceOnly: true },
{ key: 'out', name: '退出登录', icon: '/static/out.png', action: 'out' }
];
return list.filter(item => !item.deviceOnly || props.isDevice);
});
const displayFunctions = computed(() => functions.value);
const handleItemClick = (item) => {
if (item.disabled) return;
if (item.action === 'bind' && props.alreadyBindPhone) return;
if (item.action === 'sync') {
emit('sync');
} else {
emit('enter', item.action);
}
};
</script>
<style scoped lang="scss">
.function-card {
.card-header {
padding-bottom: var(--space-4);
border-bottom: 1rpx solid var(--gray-100);
}
}
.function-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-4);
padding-top: var(--space-5);
}
.function-item {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-2);
padding: var(--space-3);
border-radius: var(--radius-md);
transition: background var(--duration-fast) var(--ease-out);
&:active {
background: var(--gray-50);
}
&.is-disabled {
opacity: 0.5;
pointer-events: none;
}
}
.function-icon-wrap {
width: 88rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
background: var(--gray-50);
border-radius: var(--radius-md);
}
.function-icon {
width: 48rpx;
height: 48rpx;
}
.function-name {
font-size: 22rpx;
font-weight: 500;
color: var(--text-secondary);
text-align: center;
line-height: 1.3;
white-space: nowrap;
}
</style>