feat:添加组件类型提示;搭建基础tabbar

This commit is contained in:
weiwei committed 2025-10-22 19:31:06 +08:00
1 parent 89bb99392d
commit ca0565f720
13 files changed
+9108 -79

No files matched your search

+5 -1
View File
@@ -50,6 +50,7 @@
"@dcloudio/uni-mp-weixin": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-xhs": "3.0.0-4030620241128001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4030620241128001",
"@dcloudio/uni-ui": "^1.5.11",
"vue": "^3.4.21",
"vue-i18n": "^9.1.9"
},
@@ -59,8 +60,11 @@
"@dcloudio/uni-cli-shared": "3.0.0-4030620241128001",
"@dcloudio/uni-stacktracey": "3.0.0-4030620241128001",
"@dcloudio/vite-plugin-uni": "3.0.0-4030620241128001",
"@vue/tsconfig": "^0.1.3",
"@uni-helper/uni-app-types": "1.0.0-alpha.6",
"@uni-helper/uni-ui-types": "1.0.0-alpha.6",
"@vue/runtime-core": "^3.4.21",
"@vue/tsconfig": "^0.1.3",
"sass": "^1.93.2",
"typescript": "^4.9.4",
"vite": "5.2.8",
"vue-tsc": "^1.0.24"
+8169
View File
File diff suppressed because it is too large. Load diff
+3 -5
View File
@@ -1,10 +1,8 @@
/// <reference types='@dcloudio/types' />
import 'vue'
import "vue";
declare module '@vue/runtime-core' {
declare module "@vue/runtime-core" {
type Hooks = App.AppInstance & Page.PageInstance;
interface ComponentCustomOptions extends Hooks {
}
interface ComponentCustomOptions extends Hooks {}
}
+3 -9
View File
@@ -1,13 +1,7 @@
<script setup lang="ts">
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
onLaunch(() => {
console.log("App Launch");
});
onShow(() => {
console.log("App Show");
});
onHide(() => {
console.log("App Hide");
});
onLaunch(() => {});
onShow(() => {});
onHide(() => {});
</script>
<style></style>
+4 -4
View File
@@ -1,8 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
declare module "*.vue" {
import { DefineComponent } from "vue";
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
const component: DefineComponent<{}, {}, any>;
export default component;
}
+74 -14
View File
@@ -1,16 +1,76 @@
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
"easycom": {
"autoscan": true,
"custom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
}
},
"pages": [
{
"path": "pages/home/home",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom"
}
},
{
"path": "pages/moments/moments",
"style": {
"navigationBarTitleText": "动态",
"navigationStyle": "custom"
}
},
{
"path": "pages/messages/messages",
"style": {
"navigationBarTitleText": "消息",
"navigationStyle": "custom"
}
},
{
"path": "pages/profile/profile",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "交友聊天",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#000",
"selectedColor": "#ffd700",
"backgroundColor": "#fff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/moments/moments",
"text": "动态",
"iconPath": "static/tabbar/moments.png",
"selectedIconPath": "static/tabbar/moments-active.png"
},
{
"pagePath": "pages/messages/messages",
"text": "消息",
"iconPath": "static/tabbar/messages.png",
"selectedIconPath": "static/tabbar/messages-active.png"
},
{
"pagePath": "pages/profile/profile",
"text": "我的",
"iconPath": "static/tabbar/profile.png",
"selectedIconPath": "static/tabbar/profile-active.png"
}
]
}
}
+287
View File
@@ -0,0 +1,287 @@
<template>
<view class="home-page">
<!-- 头部导航 -->
<view class="header">
<view class="location">
<text class="location-icon">1</text>
<text class="location-text">金华市</text>
</view>
<view class="tabs">
<view class="tab active">广场</view>
<view class="tab">最近</view>
<view class="tab">新人</view>
</view>
</view>
<uni-badge text="1"></uni-badge>
<uni-badge text="2" type="success" @click=""></uni-badge>
<uni-badge text="3" type="primary" :inverted="true"></uni-badge>
<!-- 用户列表 -->
<scroll-view class="user-list" scroll-y>
<view class="user-card" v-for="user in userList" :key="user.id">
<view class="user-avatar">
<image :src="user.avatar" class="avatar-img" />
<view class="photo-count">{{ user.photoCount }}</view>
</view>
<view class="user-info">
<view class="user-name">{{ user.name }}</view>
<view class="verification">
<text class="verify-badge">✓ 真人认证</text>
</view>
<view class="user-details">
<text class="gender-age">{{ user.gender }}·{{ user.age }}岁</text>
<text class="height">{{ user.height }}cm</text>
<text class="weight">{{ user.weight }}kg</text>
<text class="occupation">{{ user.occupation }}</text>
<text class="distance">{{ user.distance }}km</text>
</view>
</view>
<view class="user-status">
<view v-if="user.isVip" class="vip-badge">
<text class="vip-text">VIP</text>
<text class="invite-text">邀请得会员</text>
</view>
<view v-else class="online-badge">在线</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { ref } from "vue";
interface User {
id: number;
name: string;
avatar: string;
gender: string;
age: number;
height: string;
weight: string;
occupation: string;
distance: string;
photoCount: number;
isVip: boolean;
}
const userList = ref<User[]>([
{
id: 1,
name: "晨晨",
avatar: "/static/avatar1.jpg",
gender: "女",
age: 25,
height: "168",
weight: "45",
occupation: "模特",
distance: "26.4",
photoCount: 2,
isVip: false,
},
{
id: 2,
name: "艾伯特",
avatar: "/static/avatar2.jpg",
gender: "女",
age: 25,
height: "160",
weight: "45",
occupation: "学生",
distance: "32.5",
photoCount: 3,
isVip: false,
},
{
id: 3,
name: "粒儿云",
avatar: "/static/avatar3.jpg",
gender: "女",
age: 23,
height: "170",
weight: "50",
occupation: "老师",
distance: "64.6",
photoCount: 2,
isVip: false,
},
{
id: 4,
name: "蓝天",
avatar: "/static/avatar4.jpg",
gender: "女",
age: 21,
height: "174",
weight: "49",
occupation: "老师",
distance: "64.5",
photoCount: 3,
isVip: true,
},
]);
</script>
<style scoped>
.home-page {
background-color: #1a1a1a;
color: #ffffff;
min-height: 100vh;
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background-color: #000;
color: #fff;
font-size: 12px;
}
.status-left {
display: flex;
align-items: center;
gap: 10px;
}
.network-info {
display: flex;
gap: 5px;
}
.status-right {
display: flex;
align-items: center;
gap: 5px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background-color: #1a1a1a;
}
.location {
display: flex;
align-items: center;
gap: 5px;
}
.tabs {
display: flex;
gap: 10px;
}
.tab {
padding: 8px 16px;
border-radius: 20px;
background-color: #333;
color: #999;
font-size: 14px;
}
.tab.active {
background-color: #ffd700;
color: #000;
}
.user-list {
flex: 1;
padding: 0 20px;
}
.user-card {
display: flex;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #333;
}
.user-avatar {
position: relative;
margin-right: 15px;
}
.avatar-img {
width: 60px;
height: 60px;
border-radius: 8px;
background-color: #333;
}
.photo-count {
position: absolute;
bottom: -5px;
left: -5px;
background-color: #ffd700;
color: #000;
padding: 2px 6px;
border-radius: 10px;
font-size: 10px;
}
.user-info {
flex: 1;
}
.user-name {
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
}
.verification {
margin-bottom: 8px;
}
.verify-badge {
background-color: #ffd700;
color: #000;
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
}
.user-details {
display: flex;
gap: 10px;
font-size: 12px;
color: #ccc;
}
.user-status {
margin-left: 10px;
}
.online-badge {
background-color: #ffd700;
color: #000;
padding: 6px 12px;
border-radius: 15px;
font-size: 12px;
}
.vip-badge {
text-align: center;
}
.vip-text {
display: block;
background-color: #ffd700;
color: #000;
padding: 4px 8px;
border-radius: 10px;
font-size: 12px;
font-weight: bold;
}
.invite-text {
display: block;
font-size: 10px;
color: #999;
margin-top: 2px;
}
</style>
-41
View File
@@ -1,41 +0,0 @@
<template>
<view class="content">
<image class="logo" src="/static/logo.png" />
<view class="text-area">
<text class="title">{{ title }}</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const title = ref('Hello')
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>
+178
View File
@@ -0,0 +1,178 @@
<template>
<view class="messages-page">
<view class="header">
<text class="title">消息</text>
<view class="badge" v-if="unreadCount > 0">{{ unreadCount }}</view>
</view>
<scroll-view class="content" scroll-y>
<view class="message-list">
<view class="message-item" v-for="message in messageList" :key="message.id">
<image :src="message.avatar" class="avatar" />
<view class="message-content">
<view class="message-header">
<text class="username">{{ message.username }}</text>
<text class="time">{{ message.time }}</text>
</view>
<view class="message-text">
<text class="text">{{ message.lastMessage }}</text>
<view class="unread-badge" v-if="message.unread > 0">{{ message.unread }}</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
interface Message {
id: number;
username: string;
avatar: string;
lastMessage: string;
time: string;
unread: number;
}
const messageList = ref<Message[]>([
{
id: 1,
username: "晨晨",
avatar: "/static/avatar1.jpg",
lastMessage: "你好,很高兴认识你!",
time: "10:30",
unread: 2,
},
{
id: 2,
username: "艾伯特",
avatar: "/static/avatar2.jpg",
lastMessage: "今天天气不错呢",
time: "09:15",
unread: 0,
},
{
id: 3,
username: "粒儿云",
avatar: "/static/avatar3.jpg",
lastMessage: "晚上一起吃饭吗?",
time: "昨天",
unread: 1,
},
{
id: 4,
username: "蓝天",
avatar: "/static/avatar4.jpg",
lastMessage: "周末有什么安排?",
time: "昨天",
unread: 0,
},
]);
const unreadCount = computed(() => {
return messageList.value.reduce((total, message) => total + message.unread, 0);
});
</script>
<style scoped>
.messages-page {
background-color: #1a1a1a;
color: #ffffff;
min-height: 100vh;
}
.header {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
border-bottom: 1px solid #333;
position: relative;
}
.title {
font-size: 18px;
font-weight: bold;
}
.badge {
position: absolute;
right: 20px;
background-color: #ff4444;
color: white;
padding: 4px 8px;
border-radius: 10px;
font-size: 12px;
font-weight: bold;
}
.content {
flex: 1;
padding: 0 20px;
}
.message-list {
padding-top: 10px;
}
.message-item {
display: flex;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #333;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 25px;
margin-right: 15px;
background-color: #333;
}
.message-content {
flex: 1;
}
.message-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.username {
font-weight: bold;
font-size: 16px;
}
.time {
font-size: 12px;
color: #999;
}
.message-text {
display: flex;
justify-content: space-between;
align-items: center;
}
.text {
color: #ccc;
font-size: 14px;
flex: 1;
}
.unread-badge {
background-color: #ff4444;
color: white;
padding: 2px 6px;
border-radius: 10px;
font-size: 12px;
font-weight: bold;
margin-left: 10px;
}
</style>
+175
View File
@@ -0,0 +1,175 @@
<template>
<view class="moments-page">
<view class="header">
<text class="title">动态</text>
</view>
<scroll-view class="content" scroll-y>
<view class="moments-list">
<view class="moment-item" v-for="moment in momentsList" :key="moment.id">
<view class="user-info">
<image :src="moment.userAvatar" class="avatar" />
<view class="user-details">
<text class="username">{{ moment.username }}</text>
<text class="time">{{ moment.time }}</text>
</view>
</view>
<view class="content">
<text class="text">{{ moment.content }}</text>
<view class="images" v-if="moment.images && moment.images.length > 0">
<image v-for="(img, index) in moment.images" :key="index" :src="img" class="moment-image" />
</view>
</view>
<view class="actions">
<view class="action-btn">
<text class="action-icon">👍</text>
<text class="action-text">{{ moment.likes }}</text>
</view>
<view class="action-btn">
<text class="action-icon">💬</text>
<text class="action-text">{{ moment.comments }}</text>
</view>
<view class="action-btn">
<text class="action-icon">🔄</text>
<text class="action-text">转发</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { ref } from "vue";
interface Moment {
id: number;
username: string;
userAvatar: string;
time: string;
content: string;
images?: string[];
likes: number;
comments: number;
}
const momentsList = ref<Moment[]>([
{
id: 1,
username: "小雨",
userAvatar: "/static/avatar1.jpg",
time: "2小时前",
content: "今天天气真好,出去走走~",
images: ["/static/moment1.jpg"],
likes: 12,
comments: 3,
},
{
id: 2,
username: "阳光",
userAvatar: "/static/avatar2.jpg",
time: "5小时前",
content: "新买的咖啡机到了,试试手冲咖啡☕",
likes: 8,
comments: 1,
},
]);
</script>
<style scoped>
.moments-page {
background-color: #1a1a1a;
color: #ffffff;
min-height: 100vh;
}
.header {
padding: 20px;
text-align: center;
border-bottom: 1px solid #333;
}
.title {
font-size: 18px;
font-weight: bold;
}
.content {
flex: 1;
padding: 0 20px;
}
.moment-item {
padding: 20px 0;
border-bottom: 1px solid #333;
}
.user-info {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 20px;
margin-right: 10px;
background-color: #333;
}
.user-details {
flex: 1;
}
.username {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
.time {
font-size: 12px;
color: #999;
}
.content {
margin-bottom: 15px;
}
.text {
line-height: 1.5;
margin-bottom: 10px;
}
.images {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.moment-image {
width: 100px;
height: 100px;
border-radius: 8px;
background-color: #333;
}
.actions {
display: flex;
gap: 20px;
}
.action-btn {
display: flex;
align-items: center;
gap: 5px;
color: #999;
font-size: 14px;
}
.action-icon {
font-size: 16px;
}
</style>
+191
View File
@@ -0,0 +1,191 @@
<template>
<view class="profile-page">
<view class="header">
<text class="title">我的</text>
</view>
<scroll-view class="content" scroll-y>
<!-- 用户信息卡片 -->
<view class="user-card">
<image :src="userInfo.avatar" class="user-avatar" />
<view class="user-info">
<text class="username">{{ userInfo.username }}</text>
<text class="user-id">ID: {{ userInfo.id }}</text>
<view class="user-tags">
<text class="tag" v-for="tag in userInfo.tags" :key="tag">{{ tag }}</text>
</view>
</view>
<view class="edit-btn">
<text class="edit-text">编辑</text>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-section">
<view class="menu-item" v-for="item in menuList" :key="item.id">
<view class="menu-icon">{{ item.icon }}</view>
<text class="menu-text">{{ item.text }}</text>
<text class="menu-arrow">></text>
</view>
</view>
<!-- 设置菜单 -->
<view class="menu-section">
<view class="menu-item" v-for="item in settingsList" :key="item.id">
<view class="menu-icon">{{ item.icon }}</view>
<text class="menu-text">{{ item.text }}</text>
<text class="menu-arrow">></text>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { ref } from "vue";
interface UserInfo {
username: string;
id: string;
avatar: string;
tags: string[];
}
interface MenuItem {
id: number;
icon: string;
text: string;
}
const userInfo = ref<UserInfo>({
username: "我的昵称",
id: "123456789",
avatar: "/static/my-avatar.jpg",
tags: ["真人认证", "VIP会员"],
});
const menuList = ref<MenuItem[]>([
{ id: 1, icon: "👤", text: "个人资料" },
{ id: 2, icon: "💎", text: "会员中心" },
{ id: 3, icon: "🎁", text: "我的礼物" },
{ id: 4, icon: "❤️", text: "我的喜欢" },
{ id: 5, icon: "👥", text: "我的关注" },
{ id: 6, icon: "📸", text: "我的相册" },
]);
const settingsList = ref<MenuItem[]>([
{ id: 7, icon: "⚙️", text: "设置" },
{ id: 8, icon: "🔒", text: "隐私设置" },
{ id: 9, icon: "🛡️", text: "安全中心" },
{ id: 10, icon: "❓", text: "帮助与反馈" },
{ id: 11, icon: "ℹ️", text: "关于我们" },
]);
</script>
<style scoped>
.profile-page {
background-color: #1a1a1a;
color: #ffffff;
min-height: 100vh;
}
.header {
padding: 20px;
text-align: center;
border-bottom: 1px solid #333;
}
.title {
font-size: 18px;
font-weight: bold;
}
.content {
flex: 1;
padding: 0 20px;
}
.user-card {
display: flex;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid #333;
}
.user-avatar {
width: 60px;
height: 60px;
border-radius: 30px;
margin-right: 15px;
background-color: #333;
}
.user-info {
flex: 1;
}
.username {
display: block;
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
}
.user-id {
display: block;
font-size: 12px;
color: #999;
margin-bottom: 10px;
}
.user-tags {
display: flex;
gap: 5px;
}
.tag {
background-color: #ffd700;
color: #000;
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
}
.edit-btn {
padding: 8px 16px;
border: 1px solid #ffd700;
border-radius: 20px;
}
.edit-text {
color: #ffd700;
font-size: 14px;
}
.menu-section {
margin: 20px 0;
}
.menu-item {
display: flex;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #333;
}
.menu-icon {
width: 30px;
font-size: 18px;
margin-right: 15px;
}
.menu-text {
flex: 1;
font-size: 16px;
}
.menu-arrow {
color: #999;
font-size: 14px;
}
</style>
+2 -2
View File
@@ -1,6 +1,6 @@
export {}
export {};
declare module "vue" {
type Hooks = App.AppInstance & Page.PageInstance;
interface ComponentCustomOptions extends Hooks {}
}
}
+17 -3
View File
@@ -1,13 +1,27 @@
{
"extends": "@vue/tsconfig/tsconfig.json",
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "bundler",
"esModuleInterop": true,
"sourceMap": true,
"skipLibCheck": true,
"importHelpers": true,
"allowSyntheticDefaultImports": true,
"useDefineForClassFields": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"lib": ["esnext", "dom"],
"types": ["@dcloudio/types"]
"types": ["@dcloudio/types", "@uni-helper/uni-app-types", "@uni-helper/uni-ui-types"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
"vueCompilerOptions": {
"plugins": ["@uni-helper/uni-app-types/volar-plugin"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "*.d.ts"],
"exclude": ["node_modules", "unpackage", "src/**/*.nvue"]
}