更新模板
This commit is contained in:
260
pages/index/device.uvue
Normal file
260
pages/index/device.uvue
Normal file
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<cl-page>
|
||||
<view class="device-page">
|
||||
<!-- 顶部状态栏适配 -->
|
||||
<view :style="{ height: (statusBarHeight + 10) + 'px' }"></view>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<view class="header">
|
||||
<text class="page-title">我的设备</text>
|
||||
<view class="add-btn" @click="addDevice">
|
||||
<cl-icon name="add-line" :size="36" color="#52c41a" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设备状态汇总 -->
|
||||
<view class="status-summary">
|
||||
<view class="status-item">
|
||||
<text class="count-online">{{ onlineCount }}</text>
|
||||
<text class="status-label">在线</text>
|
||||
</view>
|
||||
<view class="status-item">
|
||||
<text class="count-offline">{{ offlineCount }}</text>
|
||||
<text class="status-label">离线</text>
|
||||
</view>
|
||||
<view class="status-item">
|
||||
<text class="count-warning">{{ warningCount }}</text>
|
||||
<text class="status-label">告警</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设备列表 -->
|
||||
<scroll-view scroll-y class="device-list">
|
||||
<view v-for="device in deviceList" :key="device.id" class="device-card" @click="goDeviceDetail(device)">
|
||||
<view class="device-header">
|
||||
<view class="status-dot" :class="getStatusClass(device.status)"></view>
|
||||
<text class="device-name">{{ device.name }}</text>
|
||||
<text class="device-no">{{ device.deviceNo }}</text>
|
||||
</view>
|
||||
<view class="device-data">
|
||||
<view class="data-item">
|
||||
<cl-icon name="sun-line" :size="32" color="#ff4d4f" />
|
||||
<text class="data-value">{{ device.temperature || '--' }}°C</text>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<cl-icon name="cloud-line" :size="32" color="#1890ff" />
|
||||
<text class="data-value">{{ device.humidity || '--' }}%</text>
|
||||
</view>
|
||||
<view class="data-item">
|
||||
<cl-icon name="earth-line" :size="32" color="#52c41a" />
|
||||
<text class="data-value">{{ device.soilMoisture || '--' }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<cl-empty v-if="deviceList.length === 0" text="暂无设备">
|
||||
<cl-button type="primary" size="small" round @click="addDevice">添加设备</cl-button>
|
||||
</cl-empty>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 底部TabBar -->
|
||||
<custom-tabbar :current="1" />
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { router, useCool } from '@/cool';
|
||||
import CustomTabbar from '@/components/tabbar.uvue';
|
||||
|
||||
const { service } = useCool();
|
||||
|
||||
const statusBarHeight = ref(uni.getWindowInfo().statusBarHeight);
|
||||
const deviceList = ref<any[]>([]);
|
||||
|
||||
const onlineCount = computed(() => deviceList.value.filter(d => d.status === 1).length);
|
||||
const offlineCount = computed(() => deviceList.value.filter(d => d.status === 0).length);
|
||||
const warningCount = computed(() => deviceList.value.filter(d => d.status === 2).length);
|
||||
|
||||
function getStatusClass(status: number) {
|
||||
if (status === 1) return 'online';
|
||||
if (status === 0) return 'offline';
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
async function loadDevices() {
|
||||
try {
|
||||
const res = await service.nongchuang.device.list();
|
||||
if (res != null) {
|
||||
// 后端直接返回数组,不需要解析
|
||||
if (Array.isArray(res)) {
|
||||
deviceList.value = res;
|
||||
} else if (res.list != null) {
|
||||
deviceList.value = res.list as any[];
|
||||
} else {
|
||||
deviceList.value = [];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载设备失败', e);
|
||||
deviceList.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
function goDeviceDetail(device: any) {
|
||||
router.push({ path: '/pages/device/detail', query: { id: device.id } });
|
||||
}
|
||||
|
||||
function addDevice() {
|
||||
router.push('/pages/device/add');
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDevices();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.device-page {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 60rpx 30rpx 30rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
background-color: #f6ffed;
|
||||
border-radius: 35rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.status-summary {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.count-online {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.count-offline {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.count-warning {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.device-list {
|
||||
flex: 1;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.device-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.device-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.status-dot.online {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.status-dot.offline {
|
||||
background-color: #8c8c8c;
|
||||
}
|
||||
|
||||
.status-dot.warning {
|
||||
background-color: #faad14;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.device-no {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.device-data {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
margin-top: 20rpx;
|
||||
padding: 20rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.data-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.data-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,276 +1,415 @@
|
||||
<template>
|
||||
<cl-page>
|
||||
<cl-topbar
|
||||
fixed
|
||||
:height="100"
|
||||
:show-back="false"
|
||||
safe-area-top
|
||||
background-color="transparent"
|
||||
>
|
||||
<view class="flex flex-row items-center w-full flex-1 px-3">
|
||||
<view class="top-icon dark:!bg-surface-700" @tap="toSet">
|
||||
<cl-icon name="settings-line"></cl-icon>
|
||||
</view>
|
||||
<view class="my-page">
|
||||
<!-- 顶部状态栏适配 -->
|
||||
<view class="status-bar-bg" :style="{ height: (statusBarHeight + 10) + 'px' }"></view>
|
||||
|
||||
<view class="top-icon dark:!bg-surface-700" @tap="toTest">
|
||||
<cl-icon name="notification-4-line"></cl-icon>
|
||||
<!-- 用户信息头部 -->
|
||||
<view class="user-header">
|
||||
<view class="user-info" @click="goLogin">
|
||||
<image class="avatar" :src="userInfo.getString('avatar') != null ? userInfo.getString('avatar') : '/static/images/avatar.png'" mode="aspectFill" @error="userInfo['avatar'] = '/static/images/avatar.png'" />
|
||||
<view class="info">
|
||||
<text class="nickname">{{ userInfo.getString('nickname') != null ? userInfo.getString('nickname') : '点击登录' }}</text>
|
||||
<view class="id-wrapper" v-if="userInfo.get('id') != null">
|
||||
<text class="id-label">ID:</text>
|
||||
<text class="id-value">{{ userInfo.get('id') }}</text>
|
||||
</view>
|
||||
<text class="phone" v-else-if="userInfo.getString('phone') != null">{{ maskPhone(userInfo.getString('phone') as string) }}</text>
|
||||
<text class="phone" v-else-if="userInfo.getString('mobile') != null">{{ maskPhone(userInfo.getString('mobile') as string) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="edit-btn" @click="goEdit">
|
||||
<cl-icon name="edit-box-line" :size="36" color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
</cl-topbar>
|
||||
|
||||
<view class="p-3">
|
||||
<view class="flex flex-col justify-center items-center pt-6 pb-3">
|
||||
<view class="relative overflow-visible" @tap="toEdit">
|
||||
<cl-avatar
|
||||
:src="userInfo?.avatarUrl"
|
||||
:size="150"
|
||||
:pt="{ className: '!rounded-3xl', icon: { size: 60 } }"
|
||||
>
|
||||
</cl-avatar>
|
||||
|
||||
<view
|
||||
class="flex flex-col justify-center items-center absolute bottom-0 right-[-6rpx] bg-black rounded-full p-1"
|
||||
v-if="!user.isNull()"
|
||||
>
|
||||
<cl-icon name="edit-line" color="white" :size="24"></cl-icon>
|
||||
</view>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<view class="stats-section">
|
||||
<view class="stat-item" @click="goMySeeds">
|
||||
<text class="stat-value">{{ stats.seedCount != null ? stats.seedCount : 0 }}</text>
|
||||
<text class="stat-label">我的种子</text>
|
||||
</view>
|
||||
|
||||
<view class="flex-1 flex flex-col justify-center items-center w-full" @tap="toEdit">
|
||||
<cl-text :pt="{ className: '!text-xl mt-5 mb-1 font-bold' }">{{
|
||||
userInfo?.nickName ?? t("未登录")
|
||||
}}</cl-text>
|
||||
<cl-text color="info" v-if="!user.isNull()">{{ userInfo?.phone }}</cl-text>
|
||||
<view class="stat-item" @click="goMyAdoption">
|
||||
<text class="stat-value">{{ stats.adoptionCount != null ? stats.adoptionCount : 0 }}</text>
|
||||
<text class="stat-label">认养项目</text>
|
||||
</view>
|
||||
<view class="stat-item" @click="goOrders">
|
||||
<text class="stat-value">{{ stats.orderCount != null ? stats.orderCount : 0 }}</text>
|
||||
<text class="stat-label">订单</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<cl-row
|
||||
:pt="{
|
||||
className: 'pt-3 pb-6'
|
||||
}"
|
||||
>
|
||||
<cl-col :span="6">
|
||||
<view class="flex flex-col items-center justify-center">
|
||||
<cl-rolling-number
|
||||
:pt="{ className: '!text-xl' }"
|
||||
:value="171"
|
||||
></cl-rolling-number>
|
||||
<cl-text :pt="{ className: 'mt-1 !text-xs' }" color="info">{{
|
||||
t("总点击")
|
||||
}}</cl-text>
|
||||
</view>
|
||||
</cl-col>
|
||||
|
||||
<cl-col :span="6">
|
||||
<view class="flex flex-col items-center justify-center">
|
||||
<cl-rolling-number
|
||||
:pt="{ className: '!text-xl' }"
|
||||
:value="24"
|
||||
></cl-rolling-number>
|
||||
<cl-text :pt="{ className: 'mt-1 !text-xs' }" color="info">{{
|
||||
t("赞")
|
||||
}}</cl-text>
|
||||
</view>
|
||||
</cl-col>
|
||||
|
||||
<cl-col :span="6">
|
||||
<view class="flex flex-col items-center justify-center">
|
||||
<cl-rolling-number
|
||||
:pt="{ className: '!text-xl' }"
|
||||
:value="89"
|
||||
></cl-rolling-number>
|
||||
<cl-text :pt="{ className: 'mt-1 !text-xs' }" color="info">{{
|
||||
t("收藏")
|
||||
}}</cl-text>
|
||||
</view>
|
||||
</cl-col>
|
||||
|
||||
<cl-col :span="6">
|
||||
<view class="flex flex-col items-center justify-center">
|
||||
<cl-rolling-number
|
||||
:pt="{ className: '!text-xl' }"
|
||||
:value="653"
|
||||
></cl-rolling-number>
|
||||
<cl-text :pt="{ className: 'mt-1 !text-xs' }" color="info">{{
|
||||
t("粉丝")
|
||||
}}</cl-text>
|
||||
</view>
|
||||
</cl-col>
|
||||
</cl-row>
|
||||
|
||||
<cl-row :gutter="20" :pt="{ className: 'mb-3' }">
|
||||
<cl-col :span="12">
|
||||
<view class="bg-white dark:!bg-surface-800 p-4 rounded-2xl flex flex-row">
|
||||
<view class="flex flex-col mr-auto">
|
||||
<cl-text
|
||||
ellipsis
|
||||
:pt="{
|
||||
className: '!w-[180rpx]'
|
||||
}"
|
||||
>{{ t("接单模式") }}</cl-text
|
||||
>
|
||||
<cl-text :pt="{ className: '!text-xs mt-1' }" color="info">{{
|
||||
t("已关闭")
|
||||
}}</cl-text>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<view class="menu-section" v-if="myMenus.length > 0">
|
||||
<view v-for="(item, index) in myMenus" :key="index" class="menu-item" @click="onMenuClick(item)">
|
||||
<view class="menu-left">
|
||||
<cl-image v-if="isUrl(item.icon)" :src="item.icon" :size="40" />
|
||||
<cl-icon v-else :name="item.icon" :size="40" :color="item.color != null ? item.color : '#52c41a'" />
|
||||
<text class="menu-label">{{ item.label }}</text>
|
||||
</view>
|
||||
<cl-icon name="arrow-right-s-line" :size="32" color="#ccc" />
|
||||
</view>
|
||||
|
||||
<cl-switch></cl-switch>
|
||||
</view>
|
||||
</cl-col>
|
||||
|
||||
<cl-col :span="12">
|
||||
<view class="bg-white dark:!bg-surface-800 p-4 rounded-2xl flex flex-row">
|
||||
<view class="flex flex-col mr-auto">
|
||||
<cl-text
|
||||
ellipsis
|
||||
:pt="{
|
||||
className: '!w-[180rpx]'
|
||||
}"
|
||||
>{{ t("消息通知") }}</cl-text
|
||||
>
|
||||
<cl-text :pt="{ className: '!text-xs mt-1' }" color="info">{{
|
||||
t("已关闭")
|
||||
}}</cl-text>
|
||||
</view>
|
||||
|
||||
<cl-switch></cl-switch>
|
||||
</view>
|
||||
</cl-col>
|
||||
</cl-row>
|
||||
|
||||
<view class="bg-white dark:!bg-surface-800 py-5 rounded-2xl mb-3 h-[160rpx]">
|
||||
<cl-row :pt="{ className: 'overflow-visible' }">
|
||||
<cl-col :span="6">
|
||||
<view class="flex flex-col justify-center items-center px-2">
|
||||
<cl-icon name="money-cny-circle-line" :size="46"></cl-icon>
|
||||
<cl-text
|
||||
:pt="{ className: '!text-xs mt-2 text-center' }"
|
||||
color="info"
|
||||
>{{ t("待支付") }}</cl-text
|
||||
>
|
||||
</view>
|
||||
</cl-col>
|
||||
|
||||
<cl-col :span="6">
|
||||
<view class="flex flex-col justify-center items-center px-2">
|
||||
<cl-icon name="box-1-line" :size="46"></cl-icon>
|
||||
<cl-text
|
||||
:pt="{ className: '!text-xs mt-2 text-center' }"
|
||||
color="info"
|
||||
>{{ t("未发货") }}</cl-text
|
||||
>
|
||||
</view>
|
||||
</cl-col>
|
||||
|
||||
<cl-col :span="6">
|
||||
<view
|
||||
class="flex flex-col justify-center items-center relative overflow-visible px-2"
|
||||
>
|
||||
<cl-icon name="flight-takeoff-line" :size="46"></cl-icon>
|
||||
<cl-text
|
||||
:pt="{ className: '!text-xs mt-2 text-center' }"
|
||||
color="info"
|
||||
>{{ t("已发货") }}</cl-text
|
||||
>
|
||||
|
||||
<cl-badge
|
||||
type="primary"
|
||||
:value="3"
|
||||
position
|
||||
:pt="{ className: '!right-6' }"
|
||||
></cl-badge>
|
||||
</view>
|
||||
</cl-col>
|
||||
|
||||
<cl-col :span="6">
|
||||
<view class="flex flex-col justify-center items-center px-2">
|
||||
<cl-icon name="exchange-cny-line" :size="46"></cl-icon>
|
||||
<cl-text
|
||||
:pt="{ className: '!text-xs mt-2 text-center' }"
|
||||
color="info"
|
||||
>{{ t("售后 / 退款") }}</cl-text
|
||||
>
|
||||
</view>
|
||||
</cl-col>
|
||||
</cl-row>
|
||||
</view>
|
||||
|
||||
<cl-list :pt="{ className: 'mb-3' }">
|
||||
<cl-list-item
|
||||
:label="t('我的钱包')"
|
||||
icon="wallet-line"
|
||||
arrow
|
||||
hoverable
|
||||
@tap="toTest"
|
||||
>
|
||||
</cl-list-item>
|
||||
<cl-list-item
|
||||
:label="t('数据看板')"
|
||||
icon="pie-chart-line"
|
||||
arrow
|
||||
hoverable
|
||||
@tap="toTest"
|
||||
>
|
||||
</cl-list-item>
|
||||
<cl-list-item
|
||||
:label="t('历史记录')"
|
||||
icon="history-line"
|
||||
arrow
|
||||
hoverable
|
||||
@tap="toTest"
|
||||
>
|
||||
</cl-list-item>
|
||||
<cl-list-item
|
||||
:label="t('邀请好友')"
|
||||
icon="share-line"
|
||||
arrow
|
||||
hoverable
|
||||
@tap="toTest"
|
||||
>
|
||||
</cl-list-item>
|
||||
</cl-list>
|
||||
|
||||
<cl-list>
|
||||
<cl-list-item :label="t('设置')" icon="settings-line" arrow hoverable @tap="toSet">
|
||||
</cl-list-item>
|
||||
</cl-list>
|
||||
<view class="menu-section" v-else>
|
||||
<view class="menu-item" @click="goMySeeds">
|
||||
<cl-icon name="landscape-line" :size="44" color="#52c41a" />
|
||||
<text class="menu-title">我的种子</text>
|
||||
<cl-icon name="arrow-right-s-line" :size="32" color="#ccc" />
|
||||
</view>
|
||||
<view class="menu-item" @click="goMyAdoption">
|
||||
<cl-icon name="heart-line" :size="44" color="#ff7a45" />
|
||||
<text class="menu-title">我的认养</text>
|
||||
<cl-icon name="arrow-right-s-line" :size="32" color="#ccc" />
|
||||
</view>
|
||||
<view class="menu-item" @click="goOrders">
|
||||
<cl-icon name="file-list-line" :size="44" color="#1890ff" />
|
||||
<text class="menu-title">我的订单</text>
|
||||
<cl-icon name="arrow-right-s-line" :size="32" color="#ccc" />
|
||||
</view>
|
||||
<view class="menu-item" @click="goGiftCard">
|
||||
<cl-icon name="gift-line" :size="44" color="#faad14" />
|
||||
<text class="menu-title">礼品卡兑换</text>
|
||||
<cl-icon name="arrow-right-s-line" :size="32" color="#ccc" />
|
||||
</view>
|
||||
<view class="menu-item" @click="goAddress">
|
||||
<cl-icon name="map-pin-2-line" :size="44" color="#722ed1" />
|
||||
<text class="menu-title">收货地址</text>
|
||||
<cl-icon name="arrow-right-s-line" :size="32" color="#ccc" />
|
||||
</view>
|
||||
<view class="menu-item" @click="goSettings">
|
||||
<cl-icon name="settings-line" :size="44" color="#666" />
|
||||
<text class="menu-title">设置</text>
|
||||
<cl-icon name="arrow-right-s-line" :size="32" color="#ccc" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 客服与帮助 -->
|
||||
<view class="help-section">
|
||||
<view class="help-item" @click="goHelp">
|
||||
<cl-icon name="question-line" :size="36" color="#999" />
|
||||
<text class="help-text">帮助中心</text>
|
||||
</view>
|
||||
<view class="help-divider"></view>
|
||||
<view class="help-item" @click="callService">
|
||||
<cl-icon name="phone-line" :size="36" color="#999" />
|
||||
<text class="help-text">联系客服</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自定义底部导航栏 -->
|
||||
<custom-tabbar></custom-tabbar>
|
||||
|
||||
<!-- 底部TabBar -->
|
||||
<custom-tabbar :current="3" />
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { router, userInfo, useStore } from "@/cool";
|
||||
import { t } from "@/locale";
|
||||
import { useUi } from "@/uni_modules/cool-ui";
|
||||
import CustomTabbar from "@/components/tabbar.uvue";
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { router, useCool, parseObject } from '@/cool';
|
||||
import CustomTabbar from '@/components/tabbar.uvue';
|
||||
|
||||
const { user } = useStore();
|
||||
const ui = useUi();
|
||||
const { service } = useCool();
|
||||
|
||||
function toTest() {
|
||||
ui.showToast({
|
||||
message: t("开发中,敬请期待")
|
||||
});
|
||||
const statusBarHeight = ref(uni.getWindowInfo().statusBarHeight);
|
||||
const loading = ref(false);
|
||||
const userInfo = ref<any>({});
|
||||
const stats = ref<any>({
|
||||
seedCount: 0,
|
||||
adoptionCount: 0,
|
||||
orderCount: 0
|
||||
});
|
||||
const myMenus = ref<any[]>([]);
|
||||
|
||||
import { auth } from '@/utils/auth';
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
async function loadData() {
|
||||
if (!auth.isLogin.value) {
|
||||
userInfo.value = {};
|
||||
stats.value = { seedCount: 0, adoptionCount: 0, orderCount: 0 };
|
||||
return;
|
||||
}
|
||||
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: "/api/nongchuang/user/info"
|
||||
});
|
||||
if (res != null) {
|
||||
const data = res as any;
|
||||
userInfo.value = data.userInfo;
|
||||
const s = data.stats;
|
||||
if (s != null) {
|
||||
stats.value = s;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取个人信息失败', e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function toSet() {
|
||||
router.to("/pages/set/index");
|
||||
function maskPhone(phone: string) {
|
||||
if (phone.length >= 11) {
|
||||
return phone.substring(0, 3) + '****' + phone.substring(7);
|
||||
}
|
||||
return phone;
|
||||
}
|
||||
|
||||
function toEdit() {
|
||||
router.to("/pages/user/edit");
|
||||
function goLogin() {
|
||||
if (!auth.isLogin.value) {
|
||||
router.push({ path: '/pages/user/login' });
|
||||
}
|
||||
}
|
||||
|
||||
onReady(() => {
|
||||
user.get();
|
||||
// 权限检查装饰器/辅助函数
|
||||
function checkAuthAndRun(fn: Function) {
|
||||
if (!auth.isLogin.value) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '该功能需要登录后使用,是否现在前往登录?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
router.push({ path: '/pages/user/login' });
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
fn();
|
||||
}
|
||||
|
||||
function goEdit() {
|
||||
checkAuthAndRun(() => router.push({ path: '/pages/user/edit' }));
|
||||
}
|
||||
|
||||
function goMySeeds() {
|
||||
checkAuthAndRun(() => router.push({ path: '/pages/seed/my-seeds' }));
|
||||
}
|
||||
|
||||
function goMyAdoption() {
|
||||
checkAuthAndRun(() => router.push({ path: '/pages/adoption/my' }));
|
||||
}
|
||||
|
||||
function goOrders() {
|
||||
checkAuthAndRun(() => router.push({ path: '/pages/order/list' }));
|
||||
}
|
||||
|
||||
function goGiftCard() {
|
||||
checkAuthAndRun(() => router.push({ path: '/pages/adoption/gift-card' }));
|
||||
}
|
||||
|
||||
function goAddress() {
|
||||
checkAuthAndRun(() => router.push({ path: '/pages/template/shop/address' }));
|
||||
}
|
||||
|
||||
function goSettings() {
|
||||
router.push({ path: '/pages/set/index' });
|
||||
}
|
||||
|
||||
function onHelpClick(item: any) {
|
||||
uni.showToast({ title: item.label + '功能开发中', icon: 'none' });
|
||||
}
|
||||
|
||||
function isUrl(str: string | null): boolean {
|
||||
if (str == null) return false;
|
||||
return str.startsWith('http') || str.startsWith('/') || str.startsWith('wxfile');
|
||||
}
|
||||
|
||||
function goHelp() {
|
||||
uni.showToast({ title: '帮助中心开发中', icon: 'none' });
|
||||
}
|
||||
|
||||
function callService() {
|
||||
uni.makePhoneCall({ phoneNumber: '400-123-4567' });
|
||||
}
|
||||
|
||||
function onMenuClick(item : any) {
|
||||
if (item.path != null) {
|
||||
if (item.isAuth) {
|
||||
checkAuthAndRun(() => router.push({ path: item.path }));
|
||||
} else {
|
||||
router.push({ path: item.path });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
loadData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.top-icon {
|
||||
@apply flex items-center justify-center rounded-lg bg-white mr-3 p-2;
|
||||
.my-page {
|
||||
flex: 1;
|
||||
background-color: #f8f9fa;
|
||||
padding-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.user-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 40rpx 100rpx;
|
||||
background: linear-gradient(135deg, #52c41a, #95de64);
|
||||
}
|
||||
|
||||
.status-bar-bg {
|
||||
background: linear-gradient(135deg, #52c41a, #95de64);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 110rpx;
|
||||
height: 110rpx;
|
||||
border-radius: 55rpx;
|
||||
border-width: 4rpx;
|
||||
border-style: solid;
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.id-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-top: 8rpx;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.id-label, .id-value {
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.id-value {
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.phone {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #fff;
|
||||
margin: -60rpx 30rpx 20rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.menu-section {
|
||||
background-color: #fff;
|
||||
margin: 0 30rpx 20rpx;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 24rpx 30rpx;
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.help-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #fff;
|
||||
margin: 0 30rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.help-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.help-divider {
|
||||
width: 1rpx;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
363
pages/index/shop.uvue
Normal file
363
pages/index/shop.uvue
Normal file
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<cl-page>
|
||||
<view class="shop-page">
|
||||
<!-- 自定义顶部导航栏 (Fixed Top) -->
|
||||
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="navbar-content">
|
||||
<text class="page-title">农场店</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 顶部占位 (Status + NavHeight 44) -->
|
||||
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
|
||||
|
||||
<!-- 搜索栏与购物车 (Flows normally below navbar) -->
|
||||
<view class="search-row">
|
||||
<view class="search-bar" @click="goSearch">
|
||||
<cl-icon name="search-line" :size="32" color="#999" />
|
||||
<text class="search-placeholder">搜索农特产品</text>
|
||||
</view>
|
||||
|
||||
<view class="cart-btn-wrap" @click="goCart">
|
||||
<view class="cart-btn">
|
||||
<cl-icon name="shopping-cart-line" :size="40" color="#333" />
|
||||
</view>
|
||||
<view class="badge" v-if="cartCount > 0">
|
||||
<text class="badge-text">{{ cartCount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分类导航 -->
|
||||
<scroll-view scroll-x class="category-scroll" :show-scrollbar="false">
|
||||
<view class="category-list">
|
||||
<view
|
||||
v-for="cat in categories"
|
||||
:key="cat.id"
|
||||
class="category-item"
|
||||
:class="{ active: currentCategory === cat.id }"
|
||||
@click="selectCategory(cat.id)"
|
||||
>
|
||||
<text class="category-text" :class="{ 'active-text': currentCategory === cat.id }">{{ cat.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<scroll-view scroll-y class="product-scroll" @scrolltolower="loadMore">
|
||||
<view class="product-list">
|
||||
<view class="product-row">
|
||||
<view
|
||||
v-for="(product, index) in productList"
|
||||
:key="product.id"
|
||||
class="product-card"
|
||||
@click="goProductDetail(product.id)"
|
||||
>
|
||||
<image class="product-image" :src="product.mainImage || '/static/images/product.png'" mode="aspectFill" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<text class="product-origin">{{ product.origin }}</text>
|
||||
<view class="product-footer">
|
||||
<text class="product-price">¥{{ product.price }}</text>
|
||||
<view class="add-btn" @click.stop="addToCart(product)">
|
||||
<cl-icon name="add-line" :size="28" color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<cl-empty v-if="productList.length === 0" text="暂无商品" />
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 底部TabBar -->
|
||||
<custom-tabbar :current="2" />
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { router, useCool } from '@/cool';
|
||||
import CustomTabbar from '@/components/tabbar.uvue';
|
||||
|
||||
const { service } = useCool();
|
||||
|
||||
const statusBarHeight = ref(uni.getWindowInfo().statusBarHeight);
|
||||
const categories = ref<any[]>([
|
||||
{ id: 0, name: '全部' }
|
||||
]);
|
||||
|
||||
const currentCategory = ref(0);
|
||||
const productList = ref<any[]>([]);
|
||||
const cartCount = ref(2);
|
||||
|
||||
// 加载分类
|
||||
async function loadCategories() {
|
||||
try {
|
||||
const res = await service.nongchuang.category.list();
|
||||
if (Array.isArray(res)) {
|
||||
categories.value = [{ id: 0, name: '全部' }, ...res];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载分类失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProducts() {
|
||||
try {
|
||||
const params: any = {};
|
||||
if (currentCategory.value > 0) {
|
||||
params.categoryId = currentCategory.value;
|
||||
}
|
||||
const res = await service.nongchuang.product.list(params);
|
||||
if (res != null) {
|
||||
// 后端直接返回数组或包含list的对象
|
||||
if (Array.isArray(res)) {
|
||||
productList.value = res;
|
||||
} else if (res.list != null) {
|
||||
productList.value = res.list as any[];
|
||||
} else {
|
||||
productList.value = [];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载商品失败', e);
|
||||
productList.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
function selectCategory(id: number) {
|
||||
currentCategory.value = id;
|
||||
loadProducts();
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
// 加载更多
|
||||
}
|
||||
|
||||
function goSearch() {
|
||||
router.push({ path: '/pages/mall/search' });
|
||||
}
|
||||
|
||||
function goCart() {
|
||||
router.push({ path: '/pages/mall/cart' });
|
||||
}
|
||||
|
||||
function goProductDetail(id: number) {
|
||||
router.push({ path: '/pages/mall/detail', query: { id } });
|
||||
}
|
||||
|
||||
async function addToCart(product: any) {
|
||||
try {
|
||||
await service.nongchuang.cart.add({ productId: product.id, quantity: 1 });
|
||||
cartCount.value++;
|
||||
uni.showToast({ title: '已加入购物车', icon: 'success' });
|
||||
} catch (e: any) {
|
||||
const msg = e.message;
|
||||
uni.showToast({ title: msg != null ? msg : '添加失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadCategories();
|
||||
loadProducts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.shop-page {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.custom-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
height: 44px; /* Standard Nav Height */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 30rpx;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
/* 避免胶囊按钮遮挡,如果是放在顶部 */
|
||||
}
|
||||
|
||||
.cart-btn-wrap {
|
||||
position: relative;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.cart-btn {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.08); /* 增加阴影提升质感 */
|
||||
border: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: -6rpx;
|
||||
right: -6rpx;
|
||||
min-width: 32rpx;
|
||||
height: 32rpx;
|
||||
background-color: #ff4d4f;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8rpx;
|
||||
z-index: 10;
|
||||
border: 2rpx solid #fff; /* White border for separation */
|
||||
}
|
||||
|
||||
.badge-text {
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 16rpx 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 40rpx;
|
||||
height: 72rpx;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.category-scroll {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
padding: 16rpx 30rpx;
|
||||
margin-right: 16rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.category-item.active {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.category-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.active-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.product-scroll {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.product-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.product-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
width: 48%;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 280rpx;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.product-origin {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.product-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background-color: #52c41a;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user