This commit is contained in:
zpc 2025-09-06 20:17:36 +08:00
parent a8f03063d8
commit 53cb4a7b89
13 changed files with 1655 additions and 270 deletions

View File

@ -13,4 +13,9 @@ export const preloadConfigData = async () => {
configData.value = {
config: res
};
console.log("configData.value",configData.value);
};
export const getConfigData = () => {
return configData.value;
}

1
components.d.ts vendored
View File

@ -8,6 +8,7 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
MahjongCard: typeof import('./components/index/MahjongCard.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

View File

@ -0,0 +1,309 @@
<template>
<view class="grid-item" @click="handleCardClick">
<uni-card margin="0px" padding="0px" spacing="0px">
<view
style="position:absolute;top:0;right:0;width:100%; background: linear-gradient(to bottom, rgba(255, 108, 0, 0.9), rgba(255, 108, 0, 0.7));box-shadow: 0 2px 8px rgba(235,135,58, 0.3);z-index:999;border-radius:4px 4px 0 0;font-size:24rpx;color:#fff;font-weight: 500; opacity: 0.7;">
<text style="padding-left:10rpx;">{{ item.status }}</text>
</view>
<view class="item-content column">
<!-- 麻将桌区域 -->
<view class="mahjong-table">
<!-- 九宫格布局 -->
<view style="width:300rpx;height: 300rpx;">
<view class="nine-grid-container">
<!-- 第一行 -->
<view class="grid-cell"></view>
<view class="grid-cell" :class="getCellClass(0)">
<view v-if="getPlayerAtPosition(0)" class="player-avatar">
<image :src="getPlayerAtPosition(0).avatar || ''" class="avatar-img"></image>
</view>
<view v-else-if="canJoinAtPosition(0)" class="join-button" @click.stop="handleJoin">
<text class="join-text">+</text>
</view>
</view>
<view class="grid-cell"></view>
<!-- 第二行 -->
<view class="grid-cell" :class="getCellClass(1)">
<view v-if="getPlayerAtPosition(1)" class="player-avatar">
<image :src="getPlayerAtPosition(1).avatar || ''" class="avatar-img"></image>
</view>
<view v-else-if="canJoinAtPosition(1)" class="join-button" @click.stop="handleJoin">
<text class="join-text">+</text>
</view>
</view>
<view class="grid-cell center-cell">
<!-- 麻将桌背景图 -->
<image src="/static/9.png" class="table-bg" mode=""></image>
</view>
<view class="grid-cell" :class="getCellClass(2)">
<view v-if="getPlayerAtPosition(2)" class="player-avatar">
<image :src="getPlayerAtPosition(2).avatar || ''" class="avatar-img"></image>
</view>
<view v-else-if="canJoinAtPosition(2)" class="join-button" @click.stop="handleJoin">
<text class="join-text">+</text>
</view>
</view>
<!-- 第三行 -->
<view class="grid-cell"></view>
<view class="grid-cell" :class="getCellClass(3)">
<view v-if="getPlayerAtPosition(3)" class="player-avatar">
<image :src="getPlayerAtPosition(3).avatar || ''" class="avatar-img"></image>
</view>
<view v-else-if="canJoinAtPosition(3)" class="join-button" @click.stop="handleJoin">
<text class="join-text">+</text>
</view>
</view>
<view class="grid-cell"></view>
</view>
</view>
</view>
<!-- 信息区域 -->
<view class="info-section">
<text class="info-text" style="font-size: 26rpx;font-weight: 600;color: #FA690B;">{{
item.description }}</text>
<text class="info-text" style="margin-top: -7rpx;">{{ item.room }}</text>
<text class="info-text" style="font-size: 22rpx;font-weight: 600;line-height:28rpx;">2025-09-06</text>
<text class="info-text" style="font-size: 22rpx;font-weight: 600;line-height:28rpx;">{{ item.time }}</text>
<text class="info-text" style="color:#FA690B;line-height:30rpx;margin-top:6rpx;">{{ item.requirements }}</text>
</view>
</view>
</uni-card>
</view>
</template>
<script setup>
import { defineProps, defineEmits, computed } from 'vue'
// props
const props = defineProps({
item: {
type: Object,
required: true,
default: () => ({
id: '',
status: '',
description: '',
time: '',
room: '',
requirements: '',
personCount: 4,//
joinPerson: [
{
id: 1,
name: '张三',
avatar: '',
phone: '',
}
],//
})
}
})
// emits
const emit = defineEmits(['click', 'join'])
//
const playerPositions = computed(() => {
const { personCount, joinPerson } = props.item
const joinedCount = joinPerson.length
//
if (personCount === 2) {
// 2
return {
positions: [null, joinPerson[0] || null, joinPerson[1] || null, null],
joinPositions: [null, !joinPerson[0], !joinPerson[1], null]
}
} else if (personCount === 3) {
// 3
return {
positions: [joinPerson[2] || null, joinPerson[0] || null, joinPerson[1] || null, null],
joinPositions: [!joinPerson[2], !joinPerson[0], !joinPerson[1], null]
}
} else {
// 4
return {
positions: [
joinPerson[2] || null, //
joinPerson[0] || null, //
joinPerson[1] || null, //
joinPerson[3] || null //
],
joinPositions: [
!joinPerson[2], //
!joinPerson[0], //
!joinPerson[1], //
!joinPerson[3] //
]
}
}
})
//
const getPlayerAtPosition = (position) => {
return playerPositions.value.positions[position]
}
//
const canJoinAtPosition = (position) => {
const { personCount, joinPerson } = props.item
const joinedCount = joinPerson.length
//
if (joinedCount >= personCount) {
return false
}
// 使 joinPositions
return playerPositions.value.joinPositions[position]
}
//
const getCellClass = (position) => {
if (getPlayerAtPosition(position)) {
return 'player-cell'
} else if (canJoinAtPosition(position)) {
return 'join-cell'
}
return ''
}
//
const handleCardClick = () => {
emit('click', props.item)
}
//
const handleJoin = () => {
emit('join', props.item)
}
</script>
<style lang="scss" scoped>
.item-content {
height: 540rpx;
color: white;
border-radius: 10rpx;
font-size: 32rpx;
}
.mahjong-table {
width: 100%;
background-color: #D4D4D4;
border-radius: 10rpx 10rpx 0 0;
display: flex;
justify-content: center;
}
.status-tag {
position: absolute;
left: 15rpx;
top: 15rpx;
font-size: 16rpx;
color: black;
z-index: 10;
}
/* 九宫格容器 */
.nine-grid-container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
width: 100%;
height: 100%;
padding: 20rpx;
box-sizing: border-box;
}
/* 网格单元格 */
.grid-cell {
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
/* 玩家头像单元格 */
.player-cell {
display: flex;
align-items: center;
justify-content: center;
}
/* 中心单元格(麻将桌) */
.center-cell {
display: flex;
align-items: center;
justify-content: center;
}
/* 加入按钮单元格 */
.join-cell {
display: flex;
align-items: center;
justify-content: center;
}
/* 麻将桌背景图 */
.table-bg {
width: 80rpx;
height: 80rpx;
}
/* 玩家头像 */
.player-avatar {
width: 60rpx;
height: 60rpx;
background-color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.avatar-img {
width: 100%;
height: 100%;
background-color: aqua;
border-radius: 50%;
}
/* 加入按钮 */
.join-button {
width: 60rpx;
height: 60rpx;
background-color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.3s ease;
}
.join-button:hover {
transform: scale(1.1);
background-color: #f0f0f0;
}
.join-text {
color: black;
font-size: 24rpx;
font-weight: bold;
}
.info-section {
padding: 10rpx;
}
.info-text {
font-size: 24rpx;
color: black;
display: block;
}
</style>

View File

@ -43,7 +43,9 @@
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
"sdkConfigs" : {
"oauth" : {}
}
}
},
/* */

View File

@ -48,6 +48,13 @@
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "pages/me/login",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
}
],
"globalStyle": {

View File

@ -1,77 +1,31 @@
<template>
<view class="content">
<view class="content" :style="getBackgroundImg()">
<!-- 使用 z-paging 组件实现分页功能 -->
<z-paging ref="paging" v-model="dataList" @query="queryList" :refresher-enabled="true"
<z-paging ref="pagePaging" v-model="dataList" @query="queryList" :refresher-enabled="true"
:loading-more-enabled="true" :auto="true" :empty-view-text="'暂无麻将局数据'" :empty-view-img="'/static/empty.png'"
:refresher-threshold="80" :loading-more-threshold="50" style="flex: 1; width: 95%; margin-top: 10rpx;">
<view style="z-index: 100;position: sticky;top :0;">
<view class="" style="width: 100%; height: 386rpx; background-color: antiquewhite;">
<swiper class="img-swiper" :indicator-dots="false" circular autoplay interval="3000">
<swiper-item v-for="(item, index) in imgList" :key="index">
<image :src="item" mode="widthFix" class="slide-img"></image>
</swiper-item>
</swiper>
<view :style="{ height: statusBarHeight + 'px' }" style="background-color: #fff;" class="status-bar">
</view>
<view style="height: 10rpx;background-color: #fff;"></view>
</view>
<view class="grid-container">
<view class="grid-item" v-for="(item, index) in dataList" :key="index" @click="openPop()">
<view class="item-content column">
<view class="center"
style="width: 100%; height: 400rpx; background-color: #D4D4D4; border-radius: 10rpx 10rpx 0 0; position: relative;">
<text
style="position: absolute; left: 15rpx; top: 15rpx; font-size: 16rpx; color: black;">{{
item.status }}</text>
<image src="/static/9.png" style="width: 100rpx; height: 100rpx; position: absolute;"
mode=""></image>
<view class=""
style="width: 60rpx; height: 60rpx; background-color: white; position: absolute; top: 50rpx; border-radius: 50%;">
<image src=""
style="width: 100%; height: 100%; background-color: aqua; border-radius: 50%;">
</image>
</view>
<view class=""
style="width: 60rpx; height: 60rpx; background-color: white; position: absolute; left: 30rpx; border-radius: 50%;">
<image src=""
style="width: 100%; height: 100%; background-color: aqua; border-radius: 50%;">
</image>
</view>
<view class=""
style="width: 60rpx; height: 60rpx; background-color: white; position: absolute; right: 30rpx; border-radius: 50%;">
<image src=""
style="width: 100%; height: 100%; background-color: aqua; border-radius: 50%;">
</image>
</view>
<view class="center"
style="width: 60rpx; height: 60rpx; background-color: white; position: absolute; bottom: 50rpx; border-radius: 50%;">
<!-- <image src="" style="width: 100%; height: 100%; border-radius: 50%;"></image> -->
<text style="color: black;">+</text>
</view>
</view>
<text style="font-size: 24rpx; color: black; margin-top: 10rpx; margin-left: 10rpx;">{{
item.description }}</text>
<text style="font-size: 24rpx; color: black; margin-top: 10rpx; margin-left: 10rpx;">{{
item.time }}</text>
<text style="font-size: 24rpx; color: black; margin-top: 10rpx; margin-left: 10rpx;">{{
item.room }}</text>
<text style="font-size: 24rpx; color: black; margin-top: 10rpx; margin-left: 10rpx;">{{
item.requirements }}</text>
<view style="z-index: 100;margin-top:10rpx;"
v-if="homeData != null && homeData.advertList != null && homeData.advertList.length > 0">
<view style="width: 100%;width:100%;display:flex;justify-content: center;">
<view class="" style="width:97%; height: 386rpx;border-radius: 25rpx;">
<swiper class="img-swiper" :indicator-dots="false" circular autoplay interval="3000">
<swiper-item v-for="(item, index) in homeData.advertList" :key="index">
<image :src="item.imageUrl" mode="scaleToFill" class="slide-img"
style="border-radius:25rpx;"> </image>
</swiper-item>
</swiper>
</view>
</view>
<view style="height: 10rpx;"></view>
</view>
<view class="grid-container">
<MahjongCard v-for="(item, index) in dataList" :key="index" :item="item" @click="openPop"
@join="handleJoin" />
</view>
</z-paging>
@ -124,12 +78,12 @@
<text>开始时间</text>
<text>2025/08/27 1530</text>
</view>
<view class="row" style="justify-content: space-between; margin: 0 20rpx; 0">
<view class="row" style="justify-content: space-between; margin: 0 20rpx;">
<text>结束时间</text>
<text>2025/08/27 1730</text>
</view>
<text style="margin: 30rpx 20rpx; 0">合计2小时</text>
<text style="margin: 30rpx 20rpx 0;">合计2小时</text>
</view>
@ -220,136 +174,189 @@
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import {
ref,
reactive,
onMounted
} from 'vue'
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import MahjongCard from '@/components/index/MahjongCard.vue'
import {
homeData,
preloadHomeData
} from '@/common/server/index'
import {
configData,
getConfigData,
preloadConfigData
} from '@/common/server/config'
const statusBarHeight = ref(uni.getSystemInfoSync().statusBarHeight);
const getBackgroundImg = () => {
return {
background: `url(${configData.value.config.defaultImage}) 100% 100% `,
backgroundSize: '100% 100%'
};
}
//
const title = ref('Hello')
const imgList = ref([1, 2, 3, 4, 5])
const dataList = ref([]) //
const rateValue = ref(3.5)
const mockData = ref([]) //
//
const title = ref('Hello')
const imgList = ref([1, 2, 3, 4, 5])
const dataList = ref([]) //
const rateValue = ref(3.5)
const mockData = ref([]) //
//
const pagePaging = ref(null)
const cardInfo = ref(null)
const userInfo = ref(null)
//
const paging = ref(null)
const cardInfo = ref(null)
const userInfo = ref(null)
//
const initMockData = () => {
mockData.value = []
for (let i = 1; i <= 50; i++) {
//
const personCount = [2, 3, 4][Math.floor(Math.random() * 3)]
const joinedCount = Math.floor(Math.random() * (personCount + 1))
//
const initMockData = () => {
mockData.value = []
for (let i = 1; i <= 50; i++) {
mockData.value.push({
id: i,
title: `麻将局 ${i}`,
status: '组局中',
time: '2000 ~ 0430 共8小时30分钟',
room: '304包厢-大包4人',
description: '休闲局,随便来',
requirements: '不可吸烟、性别不限、信誉≧4.0'
//
const joinPerson = []
for (let j = 0; j < joinedCount; j++) {
joinPerson.push({
id: j + 1,
name: `玩家${j + 1}`,
avatar: '',
phone: `138****${String(j + 1).padStart(4, '0')}`
})
}
mockData.value.push({
id: i,
title: `麻将局 ${i}`,
status: joinedCount >= personCount ? '已满员' : '组局中',
time: '2000 ~ 0430 共8小时30分钟',
room: `304包厢-大包,${personCount}`,
description: '休闲局,随便来',
requirements: '麻将、斗地主、不可吸烟、性别不限、信誉≧4.0',
personCount: personCount,
joinPerson: joinPerson
})
}
}
//
const queryList = (pageNo, pageSize) => {
console.log(`加载第${pageNo}页,每页${pageSize}条数据`)
//
const queryList = (pageNo, pageSize) => {
console.log(`加载第${pageNo}页,每页${pageSize}条数据`)
//
setTimeout(() => {
const startIndex = (pageNo - 1) * pageSize
const endIndex = startIndex + pageSize
const pageData = mockData.value.slice(startIndex, endIndex)
//
setTimeout(() => {
const startIndex = (pageNo - 1) * pageSize
const endIndex = startIndex + pageSize
const pageData = mockData.value.slice(startIndex, endIndex)
// z-paging
paging.value.complete(pageData)
// z-paging
pagePaging.value.complete(pageData)
//
if (pageNo === 1) {
uni.showToast({
title: '刷新成功',
icon: 'success',
duration: 1500
})
}
}, 100) // 1
}
//
// if (pageNo === 1) {
// uni.showToast({
// title: '',
// icon: 'success',
// duration: 1500
// })
// }
}, 100) // 1
}
//
const refreshData = () => {
paging.value.reload()
}
//
const refreshData = () => {
pagePaging.value.reload()
}
//
const clearData = () => {
paging.value.clear()
}
//
const clearData = () => {
pagePaging.value.clear()
}
//
const openPop = () => {
cardInfo.value.open()
}
//
const openPop = () => {
cardInfo.value.open()
}
const clasePop = () => {
cardInfo.value.close()
}
const clasePop = () => {
cardInfo.value.close()
}
const openUserPop = () => {
userInfo.value.open()
}
const openUserPop = () => {
userInfo.value.open()
}
const closeUserPop = () => {
userInfo.value.close()
}
const closeUserPop = () => {
userInfo.value.close()
}
const onChange = (e) => {
console.log('rate发生改变:' + JSON.stringify(e))
}
const onChange = (e) => {
console.log('rate发生改变:' + JSON.stringify(e))
}
//
onMounted(() => {
//
initMockData()
//
const handleJoin = (item) => {
console.log('加入麻将局:', item)
// API
uni.showToast({
title: '加入成功',
icon: 'success',
duration: 1500
})
}
//
onMounted(() => {
//
initMockData()
})
onLoad(async () => {
if (!homeData.value) preloadHomeData();
if (!configData.value) preloadConfigData();
});
</script>
<style lang="scss">
.content {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
.content {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
.img-swiper {
width: 100%;
height: 386rpx;
}
}
.slide-img {
width: 100%;
height: 100%;
object-fit: cover;
background-color: aqua;
}
.img-swiper {
width: 100%;
height: 386rpx;
}
/* 网格容器 - 2列布局 */
.grid-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
/* 2列每列宽度相等 */
gap: 20rpx;
/* 网格间距(行列间距相同) */
padding: 0 10rpx;
}
.slide-img {
width: 100%;
height: 100%;
/* 网格项内容 */
.item-content {
height: 630rpx;
background-color: #E6E6E6;
color: white;
border-radius: 10rpx;
font-size: 32rpx;
}
}
/* 网格容器 - 2列布局 */
.grid-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
/* 2列每列宽度相等 */
gap: 20rpx;
/* 网格间距(行列间距相同) */
padding: 0 15rpx;
}
/* 网格项内容 */
.item-content {
height: 630rpx;
// background-color: #E6E6E6;
color: white;
border-radius: 10rpx;
font-size: 32rpx;
}
</style>

465
pages/me/login.vue Normal file
View File

@ -0,0 +1,465 @@
<template>
<view class="login-container">
<!-- 背景装饰 -->
<view class="bg-decoration">
<!-- 云朵装饰 -->
<view class="cloud cloud-1"></view>
<view class="cloud cloud-2"></view>
<view class="cloud cloud-3"></view>
<!-- 小鸟装饰 -->
<view class="bird bird-1"></view>
<view class="bird bird-2"></view>
</view>
<!-- 头部区域 -->
<view class="header">
<image src="@@:app/static/Logo.jpg" class="logo" mode="aspectFit"></image>
<text class="welcome-text">欢迎登录</text>
</view>
<!-- 登录表单 -->
<view class="login-form">
<!-- 一键登录按钮 -->
<view class="one-click-login">
<view class="quick-login-btn" @click="handleOneClickLogin" :class="{ disabled: loading }">
<text>{{ loading ? '登录中...' : '一键登录' }}</text>
</view>
</view>
<!-- 用户协议同意 -->
<view class="agreement-section">
<view class="agreement-checkbox" @click="toggleAgreement">
<checkbox :value="agreedToTerms" :checked="agreedToTerms" style="transform:scale(0.5);margin-top: -8rpx;" />
<text class="agreement-text">
我已阅读并同意
<text class="agreement-link" @click.stop="showUserAgreement">用户协议</text>
<text class="agreement-link" @click.stop="showPrivacyPolicy">隐私政策</text>
</text>
</view>
</view>
<!-- 暂不登录按钮 -->
<view class="skip-login-btn" @click="skipLogin">
<text>暂不登录</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
//
const loading = ref(false)
const agreedToTerms = ref(false)
//
const toggleAgreement = () => {
agreedToTerms.value = !agreedToTerms.value
}
const handleOneClickLogin = async () => {
if (loading.value) return
if (!agreedToTerms.value) {
uni.showToast({
title: '请先同意用户协议和隐私政策',
icon: 'none'
})
return
}
loading.value = true
try {
// API
// const response = await oneClickLoginApi()
//
await new Promise(resolve => setTimeout(resolve, 2000))
//
const randomPhone = '138' + Math.floor(Math.random() * 100000000).toString().padStart(8, '0')
//
uni.setStorageSync('userInfo', {
phone: randomPhone,
nickname: '用户' + randomPhone.slice(-4),
uid: 'U' + Date.now(),
avatar: '@@:app/nouser.png',
rating: 4.6,
reputation: 5.0,
cardQuality: 4.5,
cardSkill: 4.5,
pigeonCount: 0
})
uni.showToast({
title: '登录成功',
icon: 'success'
})
//
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
console.error('一键登录失败:', error)
uni.showToast({
title: '登录失败,请重试',
icon: 'error'
})
} finally {
loading.value = false
}
}
const skipLogin = () => {
uni.navigateBack()
}
const showUserAgreement = () => {
uni.showModal({
title: '用户协议',
content: '这里是用户协议的内容...\n\n1. 用户权利和义务\n2. 服务条款\n3. 免责声明\n4. 其他条款',
showCancel: false,
confirmText: '我知道了'
})
}
const showPrivacyPolicy = () => {
uni.showModal({
title: '隐私政策',
content: '这里是隐私政策的内容...\n\n1. 信息收集\n2. 信息使用\n3. 信息保护\n4. 信息共享',
showCancel: false,
confirmText: '我知道了'
})
}
</script>
<style lang="scss" scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(180deg, #87CEEB 0%, #98FB98 50%, #F0E68C 100%);
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.bg-decoration {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
//
.cloud {
position: absolute;
background: rgba(255, 255, 255, 0.8);
border-radius: 50rpx;
&::before,
&::after {
content: '';
position: absolute;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
}
&.cloud-1 {
width: 120rpx;
height: 40rpx;
top: 15%;
left: 10%;
&::before {
width: 50rpx;
height: 50rpx;
top: -25rpx;
left: 10rpx;
}
&::after {
width: 60rpx;
height: 60rpx;
top: -30rpx;
right: 10rpx;
}
}
&.cloud-2 {
width: 100rpx;
height: 35rpx;
top: 25%;
right: 15%;
&::before {
width: 40rpx;
height: 40rpx;
top: -20rpx;
left: 8rpx;
}
&::after {
width: 50rpx;
height: 50rpx;
top: -25rpx;
right: 8rpx;
}
}
&.cloud-3 {
width: 80rpx;
height: 30rpx;
top: 70%;
left: 20%;
&::before {
width: 35rpx;
height: 35rpx;
top: -17rpx;
left: 6rpx;
}
&::after {
width: 40rpx;
height: 40rpx;
top: -20rpx;
right: 6rpx;
}
}
}
//
.bird {
position: absolute;
width: 20rpx;
height: 15rpx;
background: #8B4513;
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
&::before {
content: '';
position: absolute;
width: 8rpx;
height: 6rpx;
background: #8B4513;
border-radius: 50%;
top: 2rpx;
left: -5rpx;
transform: rotate(-20deg);
}
&.bird-1 {
top: 20%;
right: 25%;
animation: fly 3s ease-in-out infinite;
}
&.bird-2 {
top: 60%;
left: 15%;
animation: fly 4s ease-in-out infinite reverse;
}
}
//
.flower {
position: absolute;
width: 30rpx;
height: 30rpx;
&::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
background: radial-gradient(circle, #FFB6C1 30%, #FF69B4 70%);
border-radius: 50%;
}
&::after {
content: '';
position: absolute;
width: 6rpx;
height: 20rpx;
background: #228B22;
border-radius: 3rpx;
bottom: -20rpx;
left: 50%;
transform: translateX(-50%);
}
&.flower-1 {
top: 40%;
left: 8%;
animation: sway 2s ease-in-out infinite;
}
&.flower-2 {
top: 80%;
right: 10%;
animation: sway 2.5s ease-in-out infinite reverse;
}
&.flower-3 {
top: 50%;
right: 30%;
animation: sway 3s ease-in-out infinite;
}
}
//
@keyframes fly {
0%, 100% { transform: translateX(0) translateY(0); }
25% { transform: translateX(10rpx) translateY(-5rpx); }
50% { transform: translateX(20rpx) translateY(0); }
75% { transform: translateX(10rpx) translateY(5rpx); }
}
@keyframes sway {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(2deg); }
50% { transform: rotate(0deg); }
75% { transform: rotate(-2deg); }
}
@keyframes shine {
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
}
.header {
text-align: center;
margin-bottom: 80rpx;
.logo {
width: 200rpx;
height: 200rpx;
border-radius: 50%;
margin-bottom: 30rpx;
border: 8rpx solid #FFE4B5;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.1);
}
.app-name {
display: block;
font-size: 48rpx;
font-weight: bold;
color: #8B4513;
margin-bottom: 20rpx;
text-shadow: 2rpx 2rpx 4rpx rgba(0, 0, 0, 0.1);
}
.welcome-text {
display: block;
font-size: 28rpx;
color: #228B22;
font-weight: 500;
}
}
.login-form {
padding: 0 60rpx;
width: 100%;
max-width: 600rpx;
}
.one-click-login {
margin: 40rpx 0;
.quick-login-btn {
background: linear-gradient(135deg, #F36903 0%, #E55A00 100%);
color: white;
height: 100rpx;
border-radius: 50rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: bold;
box-shadow: 0 8rpx 20rpx rgba(243, 105, 3, 0.3);
border: 4rpx solid #FFE4B5;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transform: rotate(45deg);
transition: all 0.5s;
}
&:active::before {
animation: shine 0.6s ease-in-out;
}
&.disabled {
background: linear-gradient(135deg, #D3D3D3 0%, #A9A9A9 100%);
color: #666;
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.1);
}
.quick-login-icon {
width: 40rpx;
height: 40rpx;
margin-right: 15rpx;
}
}
}
.agreement-section {
margin: 30rpx 0;
.agreement-checkbox {
display: flex;
align-items: flex-start;
.checkbox {
width: 30rpx;
height: 30rpx;
margin-right: 15rpx;
margin-top: 5rpx;
flex-shrink: 0;
}
.agreement-text {
font-size: 24rpx;
color: #8B4513;
line-height: 1.5;
.agreement-link {
color: #FF69B4;
text-decoration: underline;
font-weight: 500;
}
}
}
}
.skip-login-btn {
text-align: center;
margin: 40rpx 0;
text {
font-size: 28rpx;
color: #8B4513;
text-decoration: underline;
font-weight: 500;
}
}
</style>

View File

@ -1,49 +1,51 @@
<template>
<view class="content column">
<view class="row" style="margin-top: 150rpx; align-items: center; margin-left: 40rpx;">
<image src="" style="width: 80rpx; height: 80rpx; background-color: aquamarine; border-radius: 50%;"
mode=""></image>
<!-- <text style="margin-left: 50rpx; font-size: 24rpx;">点击登录</text> -->
<view class="column" style="margin-left: 50rpx; font-size: 24rpx;">
<view class="row" @click="toEditInfo()" style="align-items: center;">
<text>树下的胖子</text>
<image src="/static/edit_info.png" style="width: 30rpx; height: 30rpx; margin-left: 10rpx;" mode="">
</image>
</view>
<text style="margin-top: 10rpx;">UID123456</text>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-container">
<text>加载中...</text>
</view>
<view class="column" @click="openPop()"
<view class="row" style="margin-top: 150rpx; align-items: center; margin-left: 40rpx;">
<image :src="userInfo.avatar" style="width: 80rpx; height: 80rpx; border-radius: 50%;" mode=""></image>
<view class="column" style="margin-left: 50rpx; font-size: 24rpx;">
<view class="row" @click="toEditInfo()" style="align-items: center;">
<text>{{ userInfo.nickname }}</text>
</view>
<text v-if="userInfo.uid" style="margin-top: 10rpx;">UID{{ userInfo.uid }}</text>
<text v-else style="margin-top: 10rpx; color: #999;" @click="toLogin()">点击登录</text>
</view>
</view>
<view class="column" @click="currentAppointment ? openPop() : null"
style="width: 90%; border-radius: 10rpx; background-color: #F2F3F5; margin: 40rpx auto 0;">
<!-- <view class="row" style="align-items: center; margin: 20rpx;">
<!-- 无预约状态 -->
<view v-if="!currentAppointment" class="row" style="align-items: center; margin: 20rpx;">
<image src="/static/no_content.png" style="width: 150rpx; height: 150rpx;" mode=""></image>
<text style="margin-left: 40rpx;">当前没有预约</text>
</view> -->
<text style="font-size: 24rpx; margin-left: 20rpx; margin-top: 25rpx;">2025/08/13 2000 ~ 2025/08/13 2200
共2小时</text>
<text style="font-size: 24rpx; margin-left: 20rpx; margin-top: 10rpx;">803包厢-大包</text>
<view class="row"
style="font-size: 24rpx; margin-left: 20rpx; margin-top: 20rpx; align-items: center; margin-bottom: 20rpx;">
<text>已预约人员</text>
<image src="" v-for="(item,index) in 3"
style="width: 30rpx; height: 30rpx; background-color: antiquewhite; border-radius: 50%; margin-left: 10rpx;"
mode=""></image>
<text style="font-size: 24rpx; margin-left: auto; margin-right: 20rpx;">查看详情</text>
</view>
<!-- 有预约状态 -->
<template v-else>
<text style="font-size: 24rpx; margin-left: 20rpx; margin-top: 25rpx;">
{{ currentAppointment.startTime }} ~ {{ currentAppointment.endTime }} {{
currentAppointment.duration }}
</text>
<text style="font-size: 24rpx; margin-left: 20rpx; margin-top: 10rpx;">{{ currentAppointment.room
}}</text>
<view class="row"
style="font-size: 24rpx; margin-left: 20rpx; margin-top: 20rpx; align-items: center; margin-bottom: 20rpx;">
<text>已预约人员</text>
<image v-for="(participant, index) in currentAppointment.participants" :key="index"
:src="participant.avatar || ''"
style="width: 30rpx; height: 30rpx; background-color: antiquewhite; border-radius: 50%; margin-left: 10rpx;"
mode=""></image>
<text style="font-size: 24rpx; margin-left: auto; margin-right: 20rpx;">查看详情</text>
</view>
</template>
</view>
@ -52,44 +54,48 @@
<view class="row" style="align-items: center; margin-top: 30rpx; margin-left: 20rpx;">
<text style="font-size: 24rpx;">我的评分</text>
<text style="font-size: 34rpx; color: #ED3535; margin-left: 10rpx;">4.6</text>
<text v-if="userInfo.rating > 0" style="font-size: 34rpx; color: #ED3535; margin-left: 10rpx;">{{
userInfo.rating }}</text>
<text v-else style="font-size: 24rpx; color: #999; margin-left: 10rpx;">未评分</text>
<text style="font-size: 24rpx; margin-left: 180rpx;">我的信誉</text>
<text style="font-size: 34rpx; color: #ED3535; margin-left: 10rpx;">5.0</text>
<text v-if="userInfo.reputation > 0" style="font-size: 34rpx; color: #ED3535; margin-left: 10rpx;">{{
userInfo.reputation }}</text>
<text v-else style="font-size: 24rpx; color: #999; margin-left: 10rpx;">未评级</text>
</view>
<view class="row" style="align-items: center; margin-top: 20rpx; margin-left: 20rpx;">
<text style="font-size: 24rpx;">牌品</text>
<uni-rate style="margin-left: 20rpx;" :readonly="true" v-model="rateValue" />
<uni-rate style="margin-left: 20rpx;" :readonly="true" v-model="userInfo.cardQuality" />
</view>
<view class="row" style="align-items: center; margin-top: 20rpx; margin-left: 20rpx;">
<text style="font-size: 24rpx;">牌技</text>
<uni-rate style="margin-left: 20rpx;" :readonly="true" v-model="rateValue" />
<uni-rate style="margin-left: 20rpx;" :readonly="true" v-model="userInfo.cardSkill" />
</view>
<text style="font-size: 24rpx; margin: 20rpx;">鸽子数 0</text>
<text style="font-size: 24rpx; margin: 20rpx;">鸽子数 {{ userInfo.pigeonCount }}</text>
</view>
<view class="row" style="width: 90%; margin: 40rpx auto 0;">
<view class="column" @click="toAppointment()" style="align-items: center;">
<image src="/static/appoin_record.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<image src="@@:app/static/appoin_record.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<text style="font-size: 24rpx; margin-top: 20rpx;">预约记录</text>
</view>
<view class="column" style="align-items: center; margin-left: 40rpx;">
<image src="/static/problem.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<image src="@@:app/static/problem.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<text style="font-size: 24rpx; margin-top: 20rpx;">常见问题</text>
</view>
<view class="column" style="align-items: center; margin-left: 40rpx;">
<image src="/static/customer_s.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<image src="@@:app/static/customer_s.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<text style="font-size: 24rpx; margin-top: 20rpx;">联系我们</text>
</view>
<view class="column" @click="toBlacklist()" style="align-items: center; margin-left: 40rpx;">
<image src="/static/blacklist.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<image src="@@:app/static/blacklist.png" style="width: 50rpx; height: 50rpx;" mode=""></image>
<text style="font-size: 24rpx; margin-top: 20rpx;">黑名单</text>
</view>
@ -109,29 +115,31 @@
style="width: 100%; height: 180rpx; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx;">
<view class="row" style="margin-top: 20rpx; align-items: center; margin-left: 20rpx;">
<text style="font-size: 22rpx;">发起者</text>
<image src=""
<image :src="appointmentDetail.organizer.avatar"
style="width: 50rpx; height: 50rpx; background-color: antiquewhite; border-radius: 50%; margin-left: 20rpx;"
mode=""></image>
<text style="font-size: 20rpx; margin-left: 15rpx;">苏家辉</text>
<text style="font-size: 20rpx; margin-left: 15rpx;">{{ appointmentDetail.organizer.name }}</text>
</view>
<view class="row" style="margin-top: 30rpx; align-items: center; margin-left: 20rpx;">
<text style="font-size: 22rpx;">参与者</text>
<view class="row" v-for="(item,index) in 3" style="align-items: center;">
<view class="row" v-for="(participant, index) in appointmentDetail.participants" :key="index"
style="align-items: center;">
<view class="" style="position: relative; width: 50rpx; height: 50rpx; display: flex;">
<image src="" @click="openUserPop()"
<image :src="participant.avatar" @click="openUserPop()"
style="width: 50rpx; height: 50rpx; background-color: antiquewhite; border-radius: 50%; margin-left: 20rpx; position: absolute;"
mode=""></image>
<view class="center"
<view v-if="participant.isBlacklisted" class="center"
style="width: 50rpx; height: 20rpx; background-color: #FFB7B7; position: absolute; left: 40%; top: -10rpx;">
<text style="font-size: 10rpx;">黑名单</text>
</view>
</view>
<text
style="font-size: 20rpx; margin-left: 30rpx; width: 100rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">树下的胖子</text>
style="font-size: 20rpx; margin-left: 30rpx; width: 100rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{{
participant.name }}</text>
</view>
</view>
@ -144,36 +152,39 @@
<view class="row" style="justify-content: space-between; margin: 20rpx;">
<text>开始时间</text>
<text>2025/08/27 1530</text>
<text>{{ appointmentDetail.timeInfo.startTime }}</text>
</view>
<view class="row" style="justify-content: space-between; margin: 0 20rpx; 0">
<view class="row" style="justify-content: space-between; margin: 0 20rpx;">
<text>结束时间</text>
<text>2025/08/27 1730</text>
<text>{{ appointmentDetail.timeInfo.endTime }}</text>
</view>
<text style="margin: 30rpx 20rpx; 0">合计2小时</text>
<text style="margin: 30rpx 20rpx 0;">合计{{ appointmentDetail.timeInfo.duration }}</text>
</view>
<view class="column"
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">房间号304-大包30/小时</text>
<text style="margin: 10rpx 20rpx;">人数3</text>
<text style="margin: 10rpx 20rpx;">玩法类型扑克</text>
<text style="margin: 10rpx 20rpx;">具体规则斗地主</text>
<text style="margin: 10rpx 20rpx 20rpx;">补充信息</text>
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">房间号{{ appointmentDetail.roomInfo.roomNumber
}}{{ appointmentDetail.roomInfo.price }}</text>
<text style="margin: 10rpx 20rpx;">人数{{ appointmentDetail.roomInfo.playerCount }}</text>
<text style="margin: 10rpx 20rpx;">玩法类型{{ appointmentDetail.roomInfo.gameType }}</text>
<text style="margin: 10rpx 20rpx;">具体规则{{ appointmentDetail.roomInfo.rules }}</text>
<text style="margin: 10rpx 20rpx 20rpx;">补充信息{{ appointmentDetail.roomInfo.additionalInfo }}</text>
</view>
<view class="column"
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">是否禁烟禁烟</text>
<text style="margin: 10rpx 20rpx;">性别不限</text>
<text style="margin: 10rpx 20rpx 20rpx;">信誉4.0</text>
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">是否禁烟{{
appointmentDetail.requirements.smoking
}}</text>
<text style="margin: 10rpx 20rpx;">性别{{ appointmentDetail.requirements.gender }}</text>
<text style="margin: 10rpx 20rpx 20rpx;">信誉{{ appointmentDetail.requirements.reputation }}</text>
</view>
<view class="column"
style="width: 100%; background-color: #E3E2E2; margin-top: 20rpx; border-radius: 10rpx; font-size: 22rpx;">
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">鸽子费0</text>
<text style="margin: 20rpx 20rpx 10rpx; font-size: 24rpx;">鸽子费{{ appointmentDetail.pigeonFee }}</text>
<text
style="margin: 10rpx 20rpx 20rpx; color: #9F9F9F;">组局成功后若有牌友未赴约其鸽子费平均分给其他牌友组局成功或失败后鸽子费将全额返还</text>
</view>
@ -190,48 +201,228 @@
</template>
<script>
export default {
data() {
return {
rateValue: 4.5
}
<script setup>
import { ref, reactive, onMounted } from 'vue'
//
const rateValue = ref(4.5)
const infoPop = ref(null)
const loading = ref(false)
// -
const userInfo = reactive({
nickname: '未登录',
uid: '',
avatar: '@@:app/nouser.png',
rating: 0,
reputation: 0,
cardQuality: 0,
cardSkill: 0,
pigeonCount: 0
})
//
const currentAppointment = ref(null)
//
const appointmentDetail = reactive({
organizer: {
name: '苏家辉',
avatar: '',
isBlacklisted: false
},
participants: [
{
name: '树下的胖子',
avatar: '',
isBlacklisted: true
},
methods: {
{
name: '张三',
avatar: '',
isBlacklisted: false
},
{
name: '李四',
avatar: '',
isBlacklisted: false
}
],
timeInfo: {
startTime: '2025/08/27 1530',
endTime: '2025/08/27 1730',
duration: '2小时'
},
roomInfo: {
roomNumber: '304号-大包',
price: '30元/小时',
playerCount: 3,
gameType: '扑克',
rules: '斗地主',
additionalInfo: '无'
},
requirements: {
smoking: '禁烟',
gender: '不限',
reputation: '≧4.0'
},
pigeonFee: '0元'
})
openPop() {
this.$refs.infoPop.open();
},
//
const openPop = () => {
infoPop.value.open();
}
clasePop() {
this.$refs.infoPop.close();
},
const clasePop = () => {
infoPop.value.close();
}
toAppointment() {
const toAppointment = () => {
//
if (!userInfo.uid) {
uni.showModal({
title: '提示',
content: '请先登录查看预约记录',
showCancel: false,
success: () => {
uni.navigateTo({
url: '/pages/me/appointment-record-page'
});
},
toEditInfo() {
uni.navigateTo({
url: '/pages/me/edit-info'
});
},
toBlacklist() {
uni.navigateTo({
url: '/pages/me/blacklist-page'
url: '/pages/me/login'
});
}
}
});
return;
}
uni.navigateTo({
url: '/pages/me/appointment-record-page'
});
}
const toEditInfo = () => {
//
if (!userInfo.uid) {
uni.showModal({
title: '提示',
content: '请先登录',
showCancel: false,
success: () => {
uni.navigateTo({
url: '/pages/me/login'
});
}
});
return;
}
uni.navigateTo({
url: '/pages/me/edit-info'
});
}
const toBlacklist = () => {
//
if (!userInfo.uid) {
uni.showModal({
title: '提示',
content: '请先登录查看黑名单',
showCancel: false,
success: () => {
uni.navigateTo({
url: '/pages/me/login'
});
}
});
return;
}
uni.navigateTo({
url: '/pages/me/blacklist-page'
});
}
const openUserPop = () => {
//
console.log('打开用户详情弹窗');
}
const toLogin = () => {
uni.navigateTo({
url: '/pages/me/login'
});
}
// -
const loadUserData = async () => {
loading.value = true
try {
//
//
userInfo.nickname = '未登录'
userInfo.uid = ''
userInfo.rating = 0
userInfo.reputation = 0
userInfo.cardQuality = 0
userInfo.cardSkill = 0
userInfo.pigeonCount = 0
} catch (error) {
console.error('加载用户数据失败:', error)
uni.showToast({
title: '加载失败',
icon: 'error'
})
} finally {
loading.value = false
}
}
// -
const loadCurrentAppointment = async () => {
try {
//
currentAppointment.value = null
} catch (error) {
console.error('加载预约信息失败:', error)
currentAppointment.value = null
}
}
//
onMounted(() => {
loadUserData()
loadCurrentAppointment()
})
//
// onShow(() => {
// //
// const savedUserInfo = uni.getStorageSync('userInfo')
// if (savedUserInfo && savedUserInfo.uid) {
// //
// Object.assign(userInfo, savedUserInfo)
// //
// loadCurrentAppointment()
// }
// })
</script>
<style lang="scss">
.content {
width: 100%;
height: 100vh;
}
.content {
width: 100%;
height: 100vh;
}
.loading-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 20rpx 40rpx;
border-radius: 10rpx;
z-index: 9999;
}
</style>

View File

@ -0,0 +1,26 @@
## 1.3.12021-12-20
- 修复 在vue页面下略缩图显示不正常的bug
## 1.3.02021-11-19
- 重构插槽的用法 header 替换为 title
- 新增 actions 插槽
- 新增 cover 封面图属性和插槽
- 新增 padding 内容默认内边距离
- 新增 margin 卡片默认外边距离
- 新增 spacing 卡片默认内边距
- 新增 shadow 卡片阴影属性
- 取消 mode 属性,可使用组合插槽代替
- 取消 note 属性 使用actions插槽代替
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)
## 1.2.12021-07-30
- 优化 vue3下事件警告的问题
## 1.2.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.82021-07-01
- 优化 图文卡片无图片加载时,提供占位图标
- 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持)
- 修复 thumbnail 不存在仍然占位的 bug
## 1.1.72021-05-12
- 新增 组件示例地址
## 1.1.62021-02-04
- 调整为uni_modules目录规范

View File

@ -0,0 +1,270 @@
<template>
<view class="uni-card" :class="{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':border}"
:style="{'margin':isFull?0:margin,'padding':spacing,'box-shadow':isShadow?shadow:''}">
<!-- 封面 -->
<slot name="cover">
<view v-if="cover" class="uni-card__cover">
<image class="uni-card__cover-image" mode="widthFix" @click="onClick('cover')" :src="cover"></image>
</view>
</slot>
<slot name="title">
<view v-if="title || extra" class="uni-card__header">
<!-- 卡片标题 -->
<view class="uni-card__header-box" @click="onClick('title')">
<view v-if="thumbnail" class="uni-card__header-avatar">
<image class="uni-card__header-avatar-image" :src="thumbnail" mode="aspectFit" />
</view>
<view class="uni-card__header-content">
<text class="uni-card__header-content-title uni-ellipsis">{{ title }}</text>
<text v-if="title&&subTitle"
class="uni-card__header-content-subtitle uni-ellipsis">{{ subTitle }}</text>
</view>
</view>
<view class="uni-card__header-extra" @click="onClick('extra')">
<text class="uni-card__header-extra-text">{{ extra }}</text>
</view>
</view>
</slot>
<!-- 卡片内容 -->
<view class="uni-card__content" :style="{padding:padding}" @click="onClick('content')">
<slot></slot>
</view>
<view class="uni-card__actions" @click="onClick('actions')">
<slot name="actions"></slot>
</view>
</view>
</template>
<script>
/**
* Card 卡片
* @description 卡片视图组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=22
* @property {String} title 标题文字
* @property {String} subTitle 副标题
* @property {Number} padding 内容内边距
* @property {Number} margin 卡片外边距
* @property {Number} spacing 卡片内边距
* @property {String} extra 标题额外信息
* @property {String} cover 封面图本地路径需要引入
* @property {String} thumbnail 标题左侧缩略图
* @property {Boolean} is-full = [true | false] 卡片内容是否通栏 true 时将去除padding值
* @property {Boolean} is-shadow = [true | false] 卡片内容是否开启阴影
* @property {String} shadow 卡片阴影
* @property {Boolean} border 卡片边框
* @event {Function} click 点击 Card 触发事件
*/
export default {
name: 'UniCard',
emits: ['click'],
props: {
title: {
type: String,
default: ''
},
subTitle: {
type: String,
default: ''
},
padding: {
type: String,
default: '10px'
},
margin: {
type: String,
default: '15px'
},
spacing: {
type: String,
default: '0 10px'
},
extra: {
type: String,
default: ''
},
cover: {
type: String,
default: ''
},
thumbnail: {
type: String,
default: ''
},
isFull: {
//
type: Boolean,
default: false
},
isShadow: {
//
type: Boolean,
default: true
},
shadow: {
type: String,
default: '0px 0px 3px 1px rgba(0, 0, 0, 0.08)'
},
border: {
type: Boolean,
default: true
}
},
methods: {
onClick(type) {
this.$emit('click', type)
}
}
}
</script>
<style lang="scss">
$uni-border-3: #EBEEF5 !default;
$uni-shadow-base:0 0px 6px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
$uni-main-color: #3a3a3a !default;
$uni-base-color: #6a6a6a !default;
$uni-secondary-color: #909399 !default;
$uni-spacing-sm: 8px !default;
$uni-border-color:$uni-border-3;
$uni-shadow: $uni-shadow-base;
$uni-card-title: 15px;
$uni-cart-title-color:$uni-main-color;
$uni-card-subtitle: 12px;
$uni-cart-subtitle-color:$uni-secondary-color;
$uni-card-spacing: 10px;
$uni-card-content-color: $uni-base-color;
.uni-card {
margin: $uni-card-spacing;
padding: 0 $uni-spacing-sm;
border-radius: 4px;
overflow: hidden;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
background-color: #fff;
flex: 1;
.uni-card__cover {
position: relative;
margin-top: $uni-card-spacing;
flex-direction: row;
overflow: hidden;
border-radius: 4px;
.uni-card__cover-image {
flex: 1;
// width: 100%;
/* #ifndef APP-PLUS */
vertical-align: middle;
/* #endif */
}
}
.uni-card__header {
display: flex;
border-bottom: 1px $uni-border-color solid;
flex-direction: row;
align-items: center;
padding: $uni-card-spacing;
overflow: hidden;
.uni-card__header-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
overflow: hidden;
}
.uni-card__header-avatar {
width: 40px;
height: 40px;
overflow: hidden;
border-radius: 5px;
margin-right: $uni-card-spacing;
.uni-card__header-avatar-image {
flex: 1;
width: 40px;
height: 40px;
}
}
.uni-card__header-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
flex: 1;
// height: 40px;
overflow: hidden;
.uni-card__header-content-title {
font-size: $uni-card-title;
color: $uni-cart-title-color;
// line-height: 22px;
}
.uni-card__header-content-subtitle {
font-size: $uni-card-subtitle;
margin-top: 5px;
color: $uni-cart-subtitle-color;
}
}
.uni-card__header-extra {
line-height: 12px;
.uni-card__header-extra-text {
font-size: 12px;
color: $uni-cart-subtitle-color;
}
}
}
.uni-card__content {
padding: $uni-card-spacing;
font-size: 14px;
color: $uni-card-content-color;
line-height: 22px;
}
.uni-card__actions {
font-size: 12px;
}
}
.uni-card--border {
border: 1px solid $uni-border-color;
}
.uni-card--shadow {
position: relative;
/* #ifndef APP-NVUE */
box-shadow: $uni-shadow;
/* #endif */
}
.uni-card--full {
margin: 0;
border-left-width: 0;
border-left-width: 0;
border-radius: 0;
}
/* #ifndef APP-NVUE */
.uni-card--full:after {
border-radius: 0;
}
/* #endif */
.uni-ellipsis {
/* #ifndef APP-NVUE */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
/* #endif */
}
</style>

View File

@ -0,0 +1,90 @@
{
"id": "uni-card",
"displayName": "uni-card 卡片",
"version": "1.3.1",
"description": "Card 组件,提供常见的卡片样式。",
"keywords": [
"uni-ui",
"uniui",
"card",
"",
"卡片"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-icons",
"uni-scss"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,12 @@
## Card 卡片
> **组件名uni-card**
> 代码块: `uCard`
卡片视图组件。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -28,7 +28,7 @@ export default defineConfig({
/@@:([^\s"'()<>]+?\.(png|jpe?g|gif|svg|webp))/g,
(match, path) => {
count++
return `https://guyu-1308826010.cos.ap-shanghai.myqcloud.com/${path}`
return `https://admin-1308826010.cos.ap-shanghai.myqcloud.com/${path}`
}
);
if (count > 0) {