首页、登录页、引导页等页面

This commit is contained in:
zhouyanli
2026-04-23 17:30:38 +08:00
parent ebf9558713
commit 1252708a8e
50 changed files with 3375 additions and 194 deletions

View File

@@ -0,0 +1,23 @@
import request,{get,post} from "./request.js"
// =======账号密码登录========
export const getLoginPassword = (data) =>{
return post('/api/resident/auth/loginByPassword',data)
}
// 注册新用户获取验证码
export const getSendCode = (data) =>{
return post('/api/resident/auth/sendSmsCode',data)
}
// 注册新用户
export const getRegisterUser = (data) =>{
return post('/api/resident/auth/register',data)
}
// 短信验证码登录
export const getLoginBySms = (data) =>{
return post('/api/resident/auth/loginBySms',data)
}
// 忘记密码接口
export const getResetPassword = (data) =>{
return post('/api/resident/auth/resetPassword',data)
}

View File

@@ -0,0 +1,81 @@
let baseUrl = '';
if (process.env.NODE_ENV === 'development') {
baseUrl = 'http://192.168.0.224:8080'; // 开发环境
} else {
baseUrl = 'http://192.168.1.224:8080'; // 生产环境
}
export default function request(url, data = {}, method = "GET") {
return new Promise((resolve, reject) => {
// 加载中提示(提升用户体验)
uni.showLoading({ title: '请求中...', mask: true });
uni.request({
url: baseUrl + url,
data: data,
method: method,
header: {
"Authorization": "Bearer " + (uni.getStorageSync("token") || ''),
"clientid": "resident",
'content-type': 'application/json'
},
// header: {
// "token": uni.getStorageSync("token") || '',
// 'content-type': 'application/json'
// },
success: (res) => {
uni.hideLoading(); // 关闭加载提示
// 处理HTTP状态码(网络层面)
if (res.statusCode !== 200) {
const errMsg = `请求失败(${res.statusCode})`;
uni.showToast({ title: errMsg, icon: "none", duration: 2000 });
reject(errMsg);
return;
}
// 处理业务状态码(后端返回的data里)
const resData = res.data || {}; // 兼容后端返回空的情况
// 优先处理token过期(401)
if (resData.code === 401) {
uni.showToast({ title: '登录过期,请重新登录', icon: 'none', duration: 2000 });
uni.clearStorageSync('token');
setTimeout(() => {
uni.reLaunch({ url: '/pageSubPack/loginSub/pwd-login' });
}, 1500);
reject('token 过期');
return;
}
// 业务成功(code=200)
if (resData.code === 200) {
resolve(resData); // 只返回核心数据,简化页面调用
}
// 业务失败(code=500)
else if (resData.code === 500) {
const errMsg = resData.msg || '请求失败';
uni.showToast({ title: errMsg, icon: "none", duration: 2000 });
// resolve(null)
reject(errMsg);
}
else {
const errMsg = resData.msg || '业务异常';
uni.showToast({ title: errMsg, icon: "none", duration: 2000 });
reject(errMsg);
}
},
fail: (err) => {
uni.hideLoading(); // 关闭加载提示
const errMsg = err.msg || '网络请求失败';
uni.showToast({ title: errMsg, icon: "none", duration: 2000 });
reject(err);
}
});
});
}
export const get = (url, data) => request(url, data, 'GET');
export const post = (url, data) => request(url, data, 'POST');

View File

@@ -0,0 +1,197 @@
<template>
<view class="reset-pwd-page">
<view class="status_bar"></view>
<!-- 顶部导航栏 -->
<view class="nav-bar">
<uni-icons type="left" size="28" color="#333" @click="goBack"></uni-icons>
<view class="nav-title">修改密码</view>
</view>
<!-- 新密码输入框 -->
<view class="input-item">
<uni-easyinput class="input-content" type="password" v-model="newPassword"
placeholder="请输入密码" :inputBorder="false" :placeholderStyle="placeholderStyle">
</uni-easyinput>
</view>
<!-- 确认密码输入框 -->
<view class="input-item">
<uni-easyinput class="input-content" type="password" v-model="confirmPassword"
placeholder="请再次输入密码" :inputBorder="false" :placeholderStyle="placeholderStyle">
</uni-easyinput>
</view>
<!-- 确定按钮 -->
<button
class="confirm-btn"
:disabled="!canConfirm"
@click="handleConfirm"
>
确定
</button>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import {onLoad,onShow} from '@dcloudio/uni-app'
import{getResetPassword} from '../api/api.js'
// ========== 响应式数据 ==========
// 新密码
const newPassword = ref('');
// 确认密码
const confirmPassword = ref('');
const placeholderStyle = ref('font-size:24rpx')
// 定义接收上一页传来的参数
const phone = ref('');
const smsCode = ref('');
// ========== 计算属性 ==========
// 是否可点击确定按钮(密码格式正确 + 两次输入一致)
const canConfirm = computed(() => {
// 密码格式规则:6-16位任意字符(可根据需求调整)
const pwdReg = /^(?=.*[a-zA-Z])(?=.*\d).{6,16}$/;
// 校验条件:新密码符合格式 + 确认密码和新密码一致 + 均不为空
return pwdReg.test(newPassword.value)
&& newPassword.value === confirmPassword.value
&& newPassword.value && confirmPassword.value;
});
// ========== 方法 ==========
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
onShow(() => {
// 从缓存读取参数
const params = uni.getStorageSync('tempChangePwdParams') || {};
// 赋值
phone.value = params.phoneNumber
smsCode.value = params.verifyCode
// 用完删除缓存(避免下次跳转读取旧值)
uni.removeStorageSync('tempChangePwdParams');
});
// 检查表单状态(实时更新按钮状态)
const checkForm = () => {};
// 处理确定按钮点击(修改密码逻辑)
const handleConfirm = () => {
if (!canConfirm.value) return
let params = {
phone:phone.value,
smsCode:smsCode.value,
newPassword:newPassword.value,
confirmPassword:confirmPassword.value,
scene:'reset'
}
getResetPassword(params).then(res =>{
if(res.code === 200){
//uni.setStorageSync('token', res.token); // 假设后端返回token字段
uni.showToast({ title: '修改成功', icon: 'success' });
setTimeout(() =>{
uni.navigateTo({ url: '/pageSubPack/loginSub/pwd-login' });
},1500)
}else{
uni.showToast({ title: "修改失败", icon: 'error' });
}
}).catch(err =>{
uni.showToast({
title: '网络异常',
icon: 'error'
});
})
// 模拟修改密码请求
// uni.showLoading({
// title: '修改中...'
// });
// setTimeout(() => {
// uni.hideLoading();
// uni.showToast({
// title: '密码修改成功',
// icon: 'success'
// });
// // 密码修改成功后返回上一页(或跳转到登录页)
// uni.navigateTo({
// url:"/pages/login/pwd-login"
// });
// }, 1500);
};
</script>
<style scoped>
/* 页面容器 */
.reset-pwd-page {
padding: 20rpx 40rpx 40rpx;
min-height: 100vh;
background: repeating-linear-gradient(180deg, #E2F0FF, #F6FAFF );
}
.status_bar{
width: 100%;
height: 46px;
}
/* 顶部导航栏 */
.nav-bar {
display: flex;
align-items: center;
height: 88rpx;
margin-bottom: 60rpx;
}
.nav-title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 500;
color: #333;
}
/* 输入框通用样式 */
.input-item {
background-color: #fff;
border-radius: 40rpx;
padding: 0 30rpx;
height: 88rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
}
/* 输入框内容 */
.input-content {
flex: 1;
font-size: 32rpx;
color: #333;
height: 100%;
}
/* 确定按钮 */
.confirm-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #4D5DFA;
color: #fff;
border-radius: 44rpx;
font-size: 32rpx;
margin-top: 20rpx;
}
/* 确定按钮禁用状态 */
.confirm-btn:disabled {
background-color: #99c8ff;
color: #fff;
}
/* 禁用按钮边框 */
button:after{
border-radius: 50px;
}
</style>

View File

@@ -0,0 +1,229 @@
<template>
<view class="forget-page">
<view class="status_bar"></view>
<view class="nav-bar">
<uni-icons type="left" size="28" color="#333" @click="goBack"></uni-icons>
<view class="nav-title">忘记密码</view>
</view>
<!-- 手机号输入框 -->
<view class="input-item">
<view class="input-icon">
<image src="http://47.104.199.163:9090/images/login/phone.png" mode="" style="height: 100%;width: 100%;"></image>
</view>
<uni-easyinput class="input-content" type="number" v-model="phoneNumber" trim="all"
placeholder="请输入手机号" :inputBorder="false" maxlength="11" :placeholderStyle="placeholderStyle">
</uni-easyinput>
</view>
<!-- 验证码输入 + 获取验证码按钮 -->
<view class="input-item verify-item">
<view class="input-icon">
<image src="http://47.104.199.163:9090/images/login/code.png" mode="" style="height: 100%;width: 100%;"></image>
<!-- <uni-icons type="locked" size="24" color="#999"></uni-icons> -->
</view>
<uni-easyinput class="input-content verify-input" type="number" v-model="verifyCode" trim="all"
placeholder="请输入验证码" :inputBorder="false" maxlength="6" :placeholderStyle="placeholderStyle">
</uni-easyinput>
<button
class="get-verify-btn"
:disabled="!canGetVerify || countDown > 0"
@click="getVerifyCode"
>
{{ countDown > 0 ? `${countDown}s后重新获取` : '获取验证码' }}
</button>
</view>
<!-- 下一步 -->
<button
class="forget-btn"
:disabled="!canRegister"
@click="handleNext"
>
下一步
</button>
</view>
</template>
<script setup>
import { ref, computed, onUnmounted } from 'vue';
import {getSendCode} from '../api/api.js'
// ====响应式数据======
const phoneNumber = ref('')
const verifyCode = ref('')
const countDown = ref(0)
let timer = null;
const placeholderStyle = ref('font-size:24rpx')
// ========== 计算属性 ==========
// 是否可获取验证码(手机号格式正确)
const canGetVerify = computed(() => {
const phoneReg = /^1[3-9]\d{9}$/;
return phoneReg.test(phoneNumber.value);
});
// 获取验证码
const getVerifyCode = () => {
if (!canGetVerify.value) return;
let params = {
phone:phoneNumber.value,
scene:'reset'
}
getSendCode(params).then(res =>{
if(res.code === 200){
uni.showToast({
title: '验证码已发送',
icon: 'success'
});
// 开始60秒倒计时
countDown.value = 60;
timer = setInterval(() => {
countDown.value--;
if (countDown.value <= 0) {
clearInterval(timer);
timer = null;
}
}, 1000);
}else{
uni.showToast({
title: '验证码发送失败',
icon: 'error'
});
}
})
};
// 是否下一步
const canRegister = computed(() => {
const phoneReg = /^1[3-9]\d{9}$/; // 手机号11位
const verifyReg = /^\d{6}$/; // 验证码6位
return phoneReg.test(phoneNumber.value)
&& verifyReg.test(verifyCode.value)
});
// 下一步
const handleNext = () =>{
// 把参数存到本地缓存
uni.setStorageSync('tempChangePwdParams', {
phoneNumber:phoneNumber.value,
verifyCode: verifyCode.value
});
if(!canRegister.value){
uni.showToast({ title: '请输入',icon: 'error'});
return
}
uni.navigateTo({
url: '/pageSubPack/loginSub/change-pwd'
});
}
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
</script>
<style scoped>
.forget-page{
padding: 20rpx 40rpx 40rpx;
min-height: 100vh;
background: repeating-linear-gradient(180deg, #E2F0FF, #F6FAFF );
}
.status_bar{
width: 100%;
height: 46px;
}
/* 顶部导航栏 */
.nav-bar {
display: flex;
align-items: center;
/* height: 88rpx; */
margin-bottom: 70rpx;
}
.nav-title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 500;
color: #333;
}
/* 输入框通用样式 */
.input-item {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 40rpx;
padding: 0 30rpx;
height: 88rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
/* 验证码输入项特殊布局 */
.verify-item {
position: relative;
padding-right: 220rpx;
}
/* 输入框图标 */
.input-icon {
margin-right: 20rpx;
color: #999;
width: 50rpx;
height: 50rpx;
}
/* 输入框内容 */
.input-content {
flex: 1;
font-size: 32rpx;
color: #333;
height: 100%;
}
.verify-input {
padding-right: 20rpx;
}
/* 获取验证码按钮 */
.get-verify-btn {
position: absolute;
right: 10rpx;
top: 50%;
transform: translateY(-50%);
width: 200rpx;
height: 70rpx;
line-height: 70rpx;
background-color: #4D5DFA;
color: #fff;
border-radius: 35rpx;
font-size: 28rpx;
padding: 0;
margin: 0;
}
.get-verify-btn:disabled {
background-color: #ccc;
color: #fff;
}
/* 下一步按钮 */
.forget-btn{
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #4D5DFA;
color: #fff;
border-radius: 44rpx;
font-size: 32rpx;
margin-bottom: 40rpx;
}
.forget-btn:disabled{
background-color: #99c8ff;
color: #fff;
}
/* 禁用按钮边框 */
button:after{
border-radius: 50px;
}
</style>

View File

@@ -0,0 +1,359 @@
<template>
<view class="login-page">
<view class="status_bar"></view>
<!-- 登录标题 -->
<view class="login-title">手机验证码登录</view>
<!-- 手机号输入框 -->
<view class="input-item">
<view class="input-icon">
<!-- <uni-icons type="phone" size="24" color="#999"></uni-icons> -->
<image src="http://47.104.199.163:9090/images/login/phone.png" mode="" style="height: 100%;width: 100%;"></image>
</view>
<uni-easyinput class="input-content" type="number" v-model="phoneNumber"
placeholder="请输入手机号" :inputBorder="false" maxlength="11" :placeholderStyle="placeholderStyle">
</uni-easyinput>
<!-- <input
class="input-content"
type="number"
placeholder="请输入手机号"
v-model="phoneNumber"
maxlength="11"
@input="checkForm"
/> -->
</view>
<!-- 验证码输入框 + 获取验证码按钮 -->
<view class="input-item verify-item">
<view class="input-icon">
<image src="http://47.104.199.163:9090/images/login/code.png" mode="" style="height: 100%;width: 100%;"></image>
</view>
<uni-easyinput class="input-content verify-input" type="number" v-model="verifyCode"
placeholder="请输入验证码" :inputBorder="false" maxlength="6" :placeholderStyle="placeholderStyle">
</uni-easyinput>
<!-- <input
class="input-content verify-input"
type="number"
placeholder="请输入验证码"
v-model="verifyCode"
maxlength="6"
@input="checkForm"
/> -->
<button
class="get-verify-btn"
:disabled="!canGetVerify || countDown > 0"
@click="getVerifyCode"
>
{{ countDown > 0 ? `${countDown}s后重新获取` : '获取验证码' }}
</button>
</view>
<!-- 协议勾选 -->
<view class="agreement-item" @click="toggleAgreement">
<uni-icons
type="checkbox-filled"
size="24"
color="#4D5DFA"
v-if="agreeAgreement"
></uni-icons>
<uni-icons
type="checkbox"
size="24"
color="#999"
v-else
></uni-icons>
<text class="agreement-text">我已阅读并同意《用户隐私政策》</text>
</view>
<!-- 登录按钮 -->
<button
class="login-btn"
:disabled="!canLogin"
@click="handleLogin"
>
登录
</button>
<!-- 底部链接 -->
<view class="bottom-link">
<navigator class="link-item" url="/pageSubPack/loginSub/pwd-login">账号密码登录</navigator>
<navigator class="link-item" url="/pageSubPack/loginSub/register-new-user">注册新用户</navigator>
</view>
</view>
</template>
<script setup>
import { ref, computed, onUnmounted } from 'vue';
import {getLoginBySms,getSendCode} from '../api/api.js'
// ========== 响应式数据 ==========
// 手机号
const phoneNumber = ref('');
// 验证码
const verifyCode = ref('');
const placeholderStyle = ref('font-size:24rpx')
// 是否同意协议
const agreeAgreement = ref(true); // 默认勾选
// 倒计时(秒)
const countDown = ref(0);
// 定时器
let timer = null;
// ========== 计算属性 ==========
// 是否可以获取验证码(手机号格式正确)
const canGetVerify = computed(() => {
// 手机号正则:11位数字,以1开头
const phoneReg = /^1[3-9]\d{9}$/;
return phoneReg.test(phoneNumber.value);
});
// 是否可以登录(手机号+验证码+协议都满足)
const canLogin = computed(() => {
const phoneReg = /^1[3-9]\d{9}$/;
const verifyReg = /^\d{6}$/;
return phoneReg.test(phoneNumber.value) && verifyReg.test(verifyCode.value) && agreeAgreement.value;
});
// ========== 方法 ==========
// 检查表单状态
const checkForm = () => {
// 无需额外逻辑,计算属性已实时监听
};
// 切换协议勾选状态
const toggleAgreement = () => {
agreeAgreement.value = !agreeAgreement.value;
};
// 获取验证码
const getVerifyCode = () => {
if (!canGetVerify.value) return;
let params = {
phone:phoneNumber.value,
scene:'login'
}
uni.showLoading({ title: '发送中...', mask: true });
getSendCode(params).then(res =>{
if(res.code === 200){
uni.hideLoading();
uni.showToast({
title: '验证码已发送',
icon: 'success'
});
// 开始60秒倒计时
countDown.value = 60;
timer = setInterval(() => {
countDown.value--;
if (countDown.value <= 0) {
clearInterval(timer);
timer = null;
}
}, 1000);
}else{
uni.showToast({
title: '验证码发送失败',
icon: 'error'
});
}
}).catch(err => {
uni.showToast({
title: '网络异常',
icon: 'error'
});
})
};
// 登录处理
const handleLogin = () => {
if (!canLogin.value) return;
uni.showLoading({ title: '登录中...', mask: true });
let params = {
phone:phoneNumber.value,
smsCode:'123456',
scene:'login',
}
getLoginBySms(params).then(res =>{
if(res.code === 200){
if(res.data){
uni.setStorageSync('token', res.data.token);
// 存用户ID
if(res.data.userId) {
uni.setStorageSync('userId', res.data.userId);
}
// 存当前绑定的小区ID,0 表示未绑定
const currentVillageId = res.data.currentVillageId || 0;
uni.setStorageSync('currentVillageId', currentVillageId);
uni.hideLoading();
uni.showToast({ title: '登录成功', icon: 'success' });
setTimeout(() =>{
uni.switchTab({ url: '/pages/index/index' });
},1500)
}else{
uni.showToast({ title: '登录失败', icon: 'error' });
}
}else{
uni.showToast({ title:'登录失败', icon: 'error' });
}
}).catch(err => {
uni.showToast({
title: '网络异常,请重试',
icon: 'error'
});
})
// 模拟登录请求
// uni.showLoading({
// title: '登录中...'
// });
};
// ========== 生命周期 ==========
// 页面卸载时清除定时器
onUnmounted(() => {
if (timer) {
clearInterval(timer);
}
});
</script>
<style scoped>
/* 页面容器 */
.login-page {
padding: 80rpx 40rpx 40rpx;
/* background-color: #f8f9fa; */
min-height: 100vh;
background: repeating-linear-gradient(to bottom, #E2F0FF, #F6FAFF );
}
.status_bar {
height: 160px;
width: 100%;
}
/* 登录标题 */
.login-title {
font-size: 48rpx;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 80rpx;
}
/* 输入框项 */
.input-item {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 40rpx;
padding: 0 30rpx;
height: 88rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
/* 验证码输入项(特殊布局) */
.verify-item {
position: relative;
padding-right: 220rpx;
}
/* 输入框图标 */
.input-icon {
width: 50rpx;
height: 50rpx;
margin-right: 20rpx;
color: #999;
}
/* 输入框内容 */
.input-content {
flex: 1;
font-size: 32rpx;
color: #333;
height: 100%;
}
/* 验证码输入框 */
.verify-input {
padding-right: 20rpx;
}
/* 获取验证码按钮 */
.get-verify-btn {
position: absolute;
right: 10rpx;
top: 50%;
transform: translateY(-50%);
width: 200rpx;
height: 70rpx;
line-height: 70rpx;
background-color: #4D5DFA;
color: #fff;
border-radius: 35rpx;
font-size: 28rpx;
padding: 0;
margin: 0;
}
/* 获取验证码按钮禁用状态 */
.get-verify-btn:disabled {
background-color: #ccc;
color: #fff;
border-radius: 50px;
}
/* 协议勾选项 */
.agreement-item {
display: flex;
align-items: center;
margin: 40rpx 0;
padding-left: 10rpx;
}
/* 协议文本 */
.agreement-text {
font-size: 28rpx;
color: #666;
margin-left: 10rpx;
}
/* 登录按钮 */
.login-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #4D5DFA;
color: #fff;
border-radius: 44rpx;
font-size: 32rpx;
margin-bottom: 60rpx;
}
/* 登录按钮禁用状态 */
.login-btn:disabled {
background-color: #99c8ff;
color: #fff;
/* border-radius: 50px; */
}
/* 禁用按钮边框 */
button:after{
border-radius: 50px;
}
/* 底部链接 */
.bottom-link {
display: flex;
justify-content: space-between;
padding: 0 20rpx;
}
/* 链接项 */
.link-item {
font-size: 28rpx;
color: #4D5DFA;
text-decoration: none;
}
</style>

View File

@@ -0,0 +1,263 @@
<template>
<view class="login-page">
<view class="status_bar"></view>
<!-- 标题 -->
<view class="login-title">账号密码登录</view>
<!-- 手机号输入框 -->
<view class="input-item">
<view class="input-icon">
<!-- <uni-icons type="phone" size="24" color="#999"></uni-icons> -->
<image src="http://47.104.199.163:9090/images/login/phone.png" mode="" style="height: 100%;width: 100%;"></image>
</view>
<uni-easyinput class="input-content" type="text" v-model="phoneNumber"
placeholder="请输入帐号" :inputBorder="false" :placeholderStyle="placeholderStyle">
</uni-easyinput>
</view>
<!-- 密码输入框 -->
<view class="input-item">
<view class="input-icon">
<!-- <uni-icons type="locked" size="24" color="#999"></uni-icons> -->
<image src="http://47.104.199.163:9090/images/login/password.png" mode="" style="height: 100%;width: 100%;"></image>
</view>
<uni-easyinput class="input-content" type="password" v-model="password"
placeholder="请输入密码" :inputBorder="false" :placeholderStyle="placeholderStyle">
</uni-easyinput>
</view>
<!-- 协议勾选 + 忘记密码 -->
<view class="agreement-wrap">
<view class="agreement-item" @click="toggleAgreement">
<uni-icons
type="checkbox-filled"
size="24"
color="#4D5DFA"
v-if="agreeAgreement"
></uni-icons>
<uni-icons
type="checkbox"
size="24"
color="#999"
v-else
></uni-icons>
<text class="agreement-text">我已阅读并同意《用户隐私政策》</text>
</view>
<navigator class="forget-pwd" url="/pageSubPack/loginSub/forget-pwd">忘记密码?</navigator>
</view>
<!-- 登录按钮 -->
<button
class="login-btn"
:disabled="!canLogin"
@click="handleLogin"
>
登录
</button>
<!-- 底部链接 -->
<view class="bottom-link">
<navigator class="link-item" url="/pageSubPack/loginSub/login">验证码登录</navigator>
<navigator class="link-item" url="/pageSubPack/loginSub/register-new-user">注册新用户</navigator>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import {onLoad} from "@dcloudio/uni-app"
import {getLoginPassword} from '../api/api.js'
// 响应式数据
const phoneNumber = ref('');
const password = ref('');
const agreeAgreement = ref(true); // 默认勾选
const placeholderStyle = ref('font-size:24rpx')
// 计算属性:是否可登录(帐号+密码+协议都满足)
const canLogin = computed(() => {
const phoneReg = /^[a-zA-Z0-9_-]{4,16}$/; // 4-16位
const pwdReg = /^.{6,16}$/; // 密码6-16位
return phoneReg.test(phoneNumber.value) && pwdReg.test(password.value) && agreeAgreement.value;
});
// 切换协议勾选
const toggleAgreement = () => {
agreeAgreement.value = !agreeAgreement.value;
};
// 检查表单
const checkForm = () => {};
onLoad(() =>{
});
// 登录逻辑
const handleLogin = () => {
// if (!canLogin.value) {
// uni.showToast({ title: '请输入账号和密码', icon: 'none' });
// return
// }
// if (!phoneNumber.value) {
// uni.showToast({ title: '请输入账号', icon: 'none' });
// return;
// }
// if (!password.value) {
// uni.showToast({ title: '请输入密码', icon: 'none' });
// return;
// }
// if(!agreeAgreement.value){
// uni.showToast({ title: '请勾选协议', icon: 'none' });
// return;
// }
// uni.showLoading({ title: '登录中...', mask: true });
// let params = {
// username:phoneNumber.value,
// password:password.value
// }
// getLoginPassword(params).then(res =>{
// console.log('33333',res)
// if(res.code === 200){
// uni.setStorageSync('token', res.data.token);
// // 存用户ID
// if(res.data) {
// // 存当前绑定的小区ID,0 表示未绑定
// const currentVillageId = res.data.currentVillageId || 0;
// uni.setStorageSync('currentVillageId', currentVillageId);
// }
// uni.hideLoading();
// uni.showToast({ title: '登录成功', icon: 'success' });
// setTimeout(() =>{
// uni.switchTab({
// url: '/pages/index/index'
// },1000);
// })
// }else{
// uni.showToast({ title: res.msg, icon: 'error' });
// }
// }).catch(err => {
// uni.showToast({
// title: '网络异常',
// icon: 'error'
// });
// })
// 测试
uni.showLoading({
title: '登录中...',
mask:true
});
setTimeout(() => {
uni.switchTab({ url: '/pages/index/index' });
}, 1500);
uni.hideLoading()
};
</script>
<style scoped>
.login-page {
padding: 80rpx 40rpx 40rpx;
/* background-color: #f8f9fa; */
min-height: 100vh;
background: repeating-linear-gradient(180deg, #E2F0FF, #F6FAFF );
}
.status_bar {
height: 160px;
width: 100%;
}
.login-title {
font-size: 48rpx;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 80rpx;
}
.input-item {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 40rpx;
padding: 0 30rpx;
height: 88rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.input-icon {
margin-right: 20rpx;
width: 50rpx;
height: 50rpx;
}
.input-content {
flex: 1;
font-size: 28rpx;
color: #333;
height: 100%;
}
.agreement-wrap {
display: flex;
justify-content: space-between;
align-items: center;
margin: 40rpx 0;
padding: 0 10rpx;
}
.agreement-item {
display: flex;
align-items: center;
}
.agreement-text {
font-size: 28rpx;
color: #666;
margin-left: 10rpx;
}
.forget-pwd {
font-size: 28rpx;
color: #4D5DFA;
text-decoration: none;
}
.login-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #4D5DFA;
color: #fff;
border-radius: 44rpx;
font-size: 32rpx;
margin-bottom: 60rpx;
}
.login-btn:disabled {
background-color: #99c8ff;
color: #fff;
/* border-radius: 50px; */
}
/* 禁用按钮边框 */
button:after{
border-radius: 50px;
}
.bottom-link {
display: flex;
justify-content: space-between;
padding: 0 20rpx;
}
.link-item {
font-size: 28rpx;
color: #4D5DFA;
text-decoration: none;
}
</style>

View File

@@ -0,0 +1,383 @@
<template>
<view class="register-page">
<view class="status_bar"></view>
<!-- 顶部导航 -->
<view class="nav-bar">
<uni-icons type="left" size="28" color="#333" @click="goBack"></uni-icons>
<view class="nav-title">注册</view>
</view>
<!-- 注册标题 -->
<view class="register-title">注册用户</view>
<!-- 手机号输入框 -->
<view class="input-item">
<view class="input-icon">
<image src="http://47.104.199.163:9090/images/login/phone.png" mode="" style="height: 100%;width: 100%;"></image>
</view>
<uni-easyinput class="input-content" type="number" v-model="phoneNumber"
placeholder="请输入手机号" :inputBorder="false" maxlength="11" :placeholderStyle="placeholderStyle">
</uni-easyinput>
</view>
<!-- 验证码输入 + 获取验证码按钮 -->
<view class="input-item verify-item">
<view class="input-icon">
<image src="http://47.104.199.163:9090/images/login/code.png" mode="" style="height: 100%;width: 100%;"></image>
<!-- <uni-icons type="locked" size="24" color="#999"></uni-icons> -->
</view>
<uni-easyinput class="input-content verify-input" type="number" v-model="verifyCode"
placeholder="请输入验证码" :inputBorder="false" maxlength="6" :placeholderStyle="placeholderStyle">
</uni-easyinput>
<button
class="get-verify-btn"
:disabled="!canGetVerify || countDown > 0"
@click="getVerifyCode"
>
{{ countDown > 0 ? `${countDown}s后重新获取` : '获取验证码' }}
</button>
</view>
<!-- 账号输入框 -->
<view class="input-item">
<view class="input-icon">
<image src="http://47.104.199.163:9090/images/login/account.png" mode="" style="height: 100%;width: 100%;"></image>
<!-- <uni-icons type="person" size="24" color="#999"></uni-icons> -->
</view>
<uni-easyinput class="input-content" type="text" v-model="account"
placeholder="请输入账号" :inputBorder="false" :placeholderStyle="placeholderStyle">
</uni-easyinput>
</view>
<!-- 密码输入框 -->
<view class="input-item">
<view class="input-icon">
<image src="http://47.104.199.163:9090/images/login/password.png" mode="" style="height: 100%;width: 100%;"></image>
<!-- <uni-icons type="locked" size="24" color="#999"></uni-icons> -->
</view>
<uni-easyinput class="input-content" type="password" v-model="password"
placeholder="请输入密码" :inputBorder="false" :placeholderStyle="placeholderStyle" >
</uni-easyinput>
</view>
<!-- 协议勾选 -->
<view class="agreement-item" @click="toggleAgreement">
<uni-icons
type="checkbox-filled"
size="24"
color="#4D5DFA"
v-if="agreeAgreement"
></uni-icons>
<uni-icons
type="checkbox"
size="24"
color="#999"
v-else
></uni-icons>
<text class="agreement-text">我已阅读并同意《用户隐私政策》</text>
</view>
<!-- 立即注册按钮 -->
<button
class="register-btn"
:disabled="!canRegister"
@click="handleRegister"
>
立即注册
</button>
<!-- 跳转登录链接 -->
<view class="login-link">
<navigator class="link-item" url="/pageSubPack/loginSub/login">已有账号,去登录</navigator>
</view>
</view>
</template>
<script setup>
import { ref, computed, onUnmounted } from 'vue';
import {getRegisterUser,getSendCode} from '../api/api.js'
// ========== 响应式数据 ==========
// 表单数据
const phoneNumber = ref('');
const verifyCode = ref('');
const account = ref('');
const password = ref('');
const placeholderStyle = ref('font-size:24rpx')
// 协议勾选(默认勾选,和截图一致)
const agreeAgreement = ref(true);
// 验证码倒计时
const countDown = ref(0);
let timer = null;
// ========== 计算属性 ==========
// 是否可获取验证码(手机号格式正确)
const canGetVerify = computed(() => {
const phoneReg = /^1[3-9]\d{9}$/;
return phoneReg.test(phoneNumber.value);
});
// 是否可注册(所有表单项+协议都满足)
const canRegister = computed(() => {
const phoneReg = /^1[3-9]\d{9}$/; // 手机号11位
const verifyReg = /^\d{6}$/; // 验证码6位
const accountReg = /^.{4,20}$/; // 账号4-20位
const pwdReg = /^.{6,16}$/; // 密码6-16位
return phoneReg.test(phoneNumber.value)
&& verifyReg.test(verifyCode.value)
&& accountReg.test(account.value)
&& pwdReg.test(password.value)
&& agreeAgreement.value;
});
// ========== 方法 ==========
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
// 切换协议勾选状态
const toggleAgreement = () => {
agreeAgreement.value = !agreeAgreement.value;
};
// 检查表单状态(实时更新按钮状态)
const checkForm = () => {};
// 获取验证码
const getVerifyCode = () => {
if (!canGetVerify.value) return;
let params = {
phone:phoneNumber.value,
scene:'register'
}
getSendCode(params).then(res =>{
if(res.code === 200){
uni.showToast({
title: '验证码已发送',
icon: 'success'
});
// 开始60秒倒计时
countDown.value = 60;
timer = setInterval(() => {
countDown.value--;
if (countDown.value <= 0) {
clearInterval(timer);
timer = null;
}
}, 1000);
}else{
uni.showToast({
title: '验证码发送失败',
icon: 'error'
});
}
}).catch(err =>{
uni.showToast({
title: '网路异常',
icon: 'error'
});
})
};
// 处理注册逻辑
const handleRegister = () => {
if (!canRegister.value) return;
uni.showLoading({ title: '注册中...', mask: true });
let params = {
phone:phoneNumber.value,
smsCode:verifyCode.value,
username:account.value,
password:password.value
}
getRegisterUser(params).then(res =>{
if(res.code === 200){
uni.hideLoading();
uni.showToast({
title: '注册成功',
icon: 'success'
});
setTimeout(() => {
uni.redirectTo({ url: '/pageSubPack/loginSub/pwd-login' });
}, 1500);
}else{
uni.showToast({
title: '注册失败',
icon: 'error'
});
}
}).catch((err) =>{
uni.showToast({
title: '网络异常',
icon: 'error'
});
})
// 注册成功后跳转到登录页
// uni.redirectTo({
// url: '/pages/login/pwd-login'
// });
// setTimeout(() => {
// }, 1500);
};
// ========== 生命周期 ==========
// 页面卸载时清除定时器
onUnmounted(() => {
if (timer) {
clearInterval(timer);
}
});
</script>
<style scoped>
/* 页面容器 */
.register-page {
padding: 20rpx 40rpx 40rpx;
/* background-color: #f8f9fa; */
min-height: 100vh;
background: repeating-linear-gradient(180deg, #E2F0FF, #F6FAFF );
}
.status_bar {
height: 46px;
width: 100%;
}
/* 顶部导航栏 */
.nav-bar {
display: flex;
align-items: center;
/* height: 88rpx; */
margin-bottom: 70rpx;
}
.nav-title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 500;
color: #333;
}
/* 注册标题 */
.register-title {
font-size: 48rpx;
font-weight: 600;
color: #333;
margin-bottom: 60rpx;
}
/* 输入框通用样式 */
.input-item {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 40rpx;
padding: 0 30rpx;
height: 88rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
/* 验证码输入项特殊布局 */
.verify-item {
position: relative;
padding-right: 220rpx;
}
/* 输入框图标 */
.input-icon {
margin-right: 20rpx;
color: #999;
width: 50rpx;
height: 50rpx;
}
/* 输入框内容 */
.input-content {
flex: 1;
font-size: 32rpx;
color: #333;
height: 100%;
}
.verify-input {
padding-right: 20rpx;
}
/* 获取验证码按钮 */
.get-verify-btn {
position: absolute;
right: 10rpx;
top: 50%;
transform: translateY(-50%);
width: 200rpx;
height: 70rpx;
line-height: 70rpx;
background-color: #4D5DFA;
color: #fff;
border-radius: 35rpx;
font-size: 28rpx;
padding: 0;
margin: 0;
}
.get-verify-btn:disabled {
background-color: #ccc;
color: #fff;
}
/* 协议勾选项 */
.agreement-item {
display: flex;
align-items: center;
margin: 40rpx 0;
padding-left: 10rpx;
}
.agreement-text {
font-size: 28rpx;
color: #666;
margin-left: 10rpx;
}
/* 立即注册按钮 */
.register-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #4D5DFA;
color: #fff;
border-radius: 44rpx;
font-size: 32rpx;
margin-bottom: 40rpx;
}
button:after{
border-radius: 50px;
}
.register-btn:disabled {
background-color: #99c8ff;
color: #fff;
}
/* 跳转登录链接 */
.login-link {
text-align: center;
}
.link-item {
font-size: 28rpx;
color: #4D5DFA;
text-decoration: none;
}
</style>

View File

@@ -0,0 +1,133 @@
<template>
<view class="news-detail-page">
<!-- 单条资讯卡片 -->
<view class="news-card">
<!-- 标题 + 右上角未读红点 -->
<view class="news-header">
<text class="news-title">以实干担当不断开创中国特色大国外交实干担当不断开创中国特色大国外交</text>
<view class="unread-dot"></view>
</view>
<!-- 发布人 + 时间 -->
<view class="news-meta">
<text class="author">张三</text>
<text class="publish-time">2026-07-01</text>
</view>
<!-- 内容区域:浅背景卡片 -->
<view class="news-content">
<text class="content-text">
学习贯彻习近平新时代中国特色社会主义思想主题教育开展以来,外交外事战线认真学习贯彻习近平总书记重要讲话精神和党中央部署要求,全面准确把...
</text>
</view>
<!-- 详情按钮 -->
<view class="detail-btn" @click="goToDetail">
<text class="detail-text">详情</text>
<uni-icons type="right" size="14" color="#4D5DFA"></uni-icons>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
// 跳转到详情页方法
const goToDetail = () => {
uni.navigateTo({
url: '/pageSubPack/notice-list/noticeListDetails'
})
}
</script>
<style lang="scss" scoped>
// 页面基础样式
.news-detail-page {
padding: 20rpx;
background-color: #f9f9f9;
min-height: 100vh;
box-sizing: border-box;
}
// 资讯卡片:白色背景 + 圆角
.news-card {
background-color: #ffffff;
border-radius: 12rpx;
padding: 30rpx;
box-sizing: border-box;
}
// 标题行:标题 + 红点
.news-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20rpx;
}
// 资讯标题
.news-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
line-height: 1.4;
flex: 1;
margin-right: 10rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// 右上角未读红点
.unread-dot {
width: 8rpx;
height: 8rpx;
background-color: #ff3b30;
border-radius: 50%;
margin-top: 6rpx;
}
// 发布人+时间行
.news-meta {
margin-bottom: 20rpx;
}
.author {
font-size: 26rpx;
color: #666666;
margin-right: 20rpx;
}
.publish-time {
font-size: 26rpx;
color: #999999;
}
// 内容区域:浅背景卡片
.news-content {
background-color: #f5f5f5;
border-radius: 8rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.content-text {
font-size: 28rpx;
color: #333333;
line-height: 1.5;
}
// 详情按钮:蓝色文字 + 右箭头
.detail-btn {
display: flex;
align-items: center;
justify-content: space-between;
}
.detail-text {
font-size: 28rpx;
color: #4D5DFA;
margin-right: 8rpx;
}
</style>

View File

@@ -0,0 +1,531 @@
<template>
<view class="container">
<!-- 内容区域 -->
<!-- <scroll-view class="content-scroll" scroll-y> -->
<!-- 文章标题 -->
<view class="article-header">
<view class="article-title">
{{noticeDetail.noticeTitle}}
</view>
</view>
<!-- 作者和日期 -->
<view class="notice-meta">
<view class="notice-author">
<text class="author-name">{{noticeDetail.author}}</text>
<text class="separator">·</text>
<text class="publish-time">{{noticeDetail.publishTime}}</text>
</view>
</view>
<!-- 文章内容 -->
<view class="article-content">
<view class="paragraph">
{{noticeDetail.noticeContent}}
</view>
<view class="articleimg">
<image src="http://47.104.199.163:9090/images/home/notice-1.png" mode="" style="width: 100%;height: 100%;border-radius: 20rpx;"></image>
<image src="http://47.104.199.163:9090/images/home/notice-2.png" mode="" style="width: 100%;height: 100%;border-radius: 20rpx;"></image>
</view>
</view>
<!-- 附件区域 -->
<!-- <view class="attachments-section">
<view class="section-title">
<view class="title-text">相关附件</view>
<view class="title-line"></view>
</view>
<view class="attachments-list"> -->
<!-- DOC文件 -->
<!-- <view class="attachment-item" @click="downloadFile('filename.DOC')">
<view class="attachment-left">
<view class="file-icon doc-icon">📄</view>
<view class="file-info">
<view class="file-name">文件名.DOC</view>
<view class="file-size">299kb</view>
</view>
</view>
<view class="download-btn">
<view class="download-icon">↓</view>
</view>
</view> -->
<!-- PDF文件 -->
<!-- <view class="attachment-item" @click="downloadFile('filename.PDF')">
<view class="attachment-left">
<view class="file-icon pdf-icon">📄</view>
<view class="file-info">
<view class="file-name">文件名.PDF</view>
<view class="file-size">299kb</view>
</view>
</view>
<view class="download-btn">
<view class="download-icon">↓</view>
</view>
</view>
</view>
</view> -->
<!-- </scroll-view> -->
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
// import {getNoticeDetails} from '../api/api.js'
const isLoading = ref(false)
const noticeDetail = ref({})
const currentNoticeId = ref('') // 接收公告跳转的id
// =====方法====
onLoad((options) =>{
currentNoticeId.value = options?.id
// console.log('currentNoticeId',currentNoticeId.value)
if(currentNoticeId.value){
getDetailsData(currentNoticeId.value)
}
// console.log('currentNoticeId',currentNoticeId.value)
})
// 获取详情数据
const getDetailsData = async (noticeId) =>{
try{
const res = await getNoticeDetails(noticeId)
if(res.code === 200){
noticeDetail.value = res.data || {}
uni.$emit('noticeRead',{
noticeId:noticeId,
readStatus:noticeDetail.value.readStatus
})
}
}catch(err){
uni.showToast({
title:"加载失败",
icon:"none"
})
}
}
// 下载文件
const downloadFile = (fileName) => {
uni.showModal({
title: '下载确认',
content: `确定要下载 "${fileName}" 吗?`,
success: (res) => {
if (res.confirm) {
isLoading.value = true
// 显示下载进度
uni.showLoading({
title: '下载中...',
mask: true
})
// 模拟下载过程
setTimeout(() => {
uni.hideLoading()
isLoading.value = false
// 根据文件类型打开文件
if (fileName.endsWith('.PDF')) {
uni.showToast({
title: 'PDF文件已保存到下载目录',
icon: 'success',
duration: 2000
})
// 在真实项目中,这里应该调用下载API
// uni.downloadFile({
// url: 'https://example.com/files/filename.PDF',
// success: (res) => {
// if (res.statusCode === 200) {
// uni.saveFile({
// tempFilePath: res.tempFilePath,
// success: (saveRes) => {
// uni.showToast({ title: '下载成功', icon: 'success' })
// }
// })
// }
// }
// })
} else if (fileName.endsWith('.DOC')) {
uni.showToast({
title: 'DOC文件已保存到下载目录',
icon: 'success',
duration: 2000
})
}
}, 1500)
}
}
})
}
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
background-color: #ffffff;
padding: 20px;
// font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* 内容滚动区域 */
.content-scroll {
padding-top: 15px;
box-sizing: border-box;
background-color: #ffffff;
}
/* 文章头部 */
.article-header {
// padding: 24px 20px 20px;
// border-bottom: 1px solid #f0f0f0;
.article-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
line-height: 1.4;
margin-bottom: 16px;
text-align: center;
}
.article-meta {
display: flex;
align-items: center;
justify-content: center;
color: #666666;
font-size: 14px;
.meta-item {
display: flex;
align-items: center;
.meta-icon {
font-size: 14px;
margin-right: 4px;
}
.meta-text {
font-size: 14px;
}
}
.meta-divider {
margin: 0 12px;
color: #cccccc;
}
}
}
/* 元信息 */
.notice-meta {
display: flex;
align-items: center;
justify-content: center;
// margin-bottom: 12px;
color: #666;
font-size: 16px;
margin: 0 30rpx;
.notice-author {
display: flex;
align-items: center;
.author-name {
color: #666;
}
.separator {
margin: 0 6px;
color: #ccc;
}
.publish-time {
color: #999;
}
}
.notice-time {
display: flex;
align-items: center;
.date-text {
margin-left: 4px;
color: #999;
}
}
}
/* 文章内容 */
.article-content {
padding: 20px;
color: #333333;
line-height: 1.8;
font-size: 16px;
.articleimg{
width: 100%;
height: 249rpx;
}
.paragraph {
margin-bottom: 20px;
text-align: justify;
text-indent: 2em;
}
.bullet-list {
margin: 20px 0 20px 2em;
.bullet-item {
display: flex;
margin-bottom: 12px;
.bullet-dot {
color: #666666;
margin-right: 8px;
flex-shrink: 0;
}
.bullet-text {
flex: 1;
color: #333333;
}
}
}
}
/* 附件区域 */
.attachments-section {
padding: 20px;
background-color: #f9f9f9;
border-top: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
margin-top: 20px;
.section-title {
display: flex;
align-items: center;
margin-bottom: 20px;
.title-text {
font-size: 18px;
font-weight: 600;
color: #333333;
white-space: nowrap;
margin-right: 12px;
}
.title-line {
flex: 1;
height: 1px;
background-color: #e0e0e0;
}
}
.attachments-list {
.attachment-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
background-color: #ffffff;
border-radius: 8px;
margin-bottom: 12px;
border: 1px solid #e8e8e8;
&:active {
background-color: #f5f5f5;
}
&:last-child {
margin-bottom: 0;
}
.attachment-left {
display: flex;
align-items: center;
flex: 1;
.file-icon {
font-size: 24px;
margin-right: 12px;
&.doc-icon {
color: #2b579a; /* Word文档蓝色 */
}
&.pdf-icon {
color: #e74c3c; /* PDF红色 */
}
}
.file-info {
.file-name {
font-size: 16px;
color: #333333;
margin-bottom: 4px;
font-weight: 500;
}
.file-size {
font-size: 13px;
color: #666666;
}
}
}
.download-btn {
width: 40px;
height: 40px;
border-radius: 20px;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
.download-icon {
font-size: 18px;
color: #333333;
}
}
}
}
}
/* 页面底部 */
.page-footer {
padding: 30px 20px 40px;
display: flex;
flex-direction: column;
align-items: center;
.footer-text {
font-size: 16px;
color: #999999;
margin-bottom: 20px;
}
.footer-info {
display: flex;
align-items: center;
font-size: 13px;
color: #999999;
.info-item {
padding: 0 8px;
}
.info-divider {
color: #dddddd;
}
}
}
/* 响应式调整 */
@media (min-width: 768px) {
.container {
max-width: 768px;
margin: 0 auto;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
.article-header,
.article-content,
.attachments-section,
.page-footer {
padding-left: 40px;
padding-right: 40px;
}
}
/* 暗色模式适配 */
@media (prefers-color-scheme: dark) {
.container {
background-color: #1a1a1a;
}
.custom-navbar {
background-color: #1a1a1a;
border-bottom-color: #333333;
.nav-back,
.nav-title {
color: #ffffff;
}
.nav-actions .action-icon {
color: #ffffff;
}
}
.content-scroll {
background-color: #1a1a1a;
}
.article-header {
border-bottom-color: #333333;
.article-title {
color: #ffffff;
}
.article-meta {
color: #999999;
}
}
.article-content {
color: #e0e0e0;
.bullet-item .bullet-text {
color: #e0e0e0;
}
}
.attachments-section {
background-color: #2a2a2a;
border-top-color: #333333;
border-bottom-color: #333333;
.section-title .title-text {
color: #ffffff;
}
.attachment-item {
background-color: #2a2a2a;
border-color: #333333;
&:active {
background-color: #333333;
}
.file-info .file-name {
color: #ffffff;
}
.file-info .file-size {
color: #999999;
}
}
}
.page-footer {
.footer-text,
.footer-info {
color: #666666;
}
}
}
</style>

View File

@@ -15,7 +15,7 @@
<uni-icons type="clear" size="18" color="#999"></uni-icons> <uni-icons type="clear" size="18" color="#999"></uni-icons>
</view> </view>
</view> </view>
<button class="search-btn" @tap="handleSearch">搜索</button> <!-- <button class="search-btn" @tap="handleSearch">搜索</button> -->
</view> </view>
<!-- 小区列表 --> <!-- 小区列表 -->
@@ -34,7 +34,8 @@
@tap="selectCommunity(item)" @tap="selectCommunity(item)"
> >
<view class="community-item-left"> <view class="community-item-left">
<uni-icons class="house-icon" type="home" size="24" color="#666"></uni-icons> <image src="/static/imgs/community.png" style="width: 48rpx;height: 48rpx;"></image>
<!-- <uni-icons class="house-icon" type="home" size="24" color="#666"></uni-icons> -->
<view class="community-info"> <view class="community-info">
<text class="community-name">{{ item.name }}</text> <text class="community-name">{{ item.name }}</text>
<!-- <text class="community-address">{{ item.address }}</text> --> <!-- <text class="community-address">{{ item.address }}</text> -->
@@ -42,7 +43,8 @@
</view> </view>
</view> </view>
<view v-if="selectedItem && selectedItem.id === item.id" class="selected-icon"> <view v-if="selectedItem && selectedItem.id === item.id" class="selected-icon">
<uni-icons type="checkbox-filled" size="24" color="#007AFF"></uni-icons> <!-- <uni-icons type="checkbox-filled" size="24" color="#007AFF"></uni-icons> -->
<image src="/static/imgs/check.png" style="width: 48rpx;height: 48rpx;"></image>
</view> </view>
</view> </view>
@@ -88,9 +90,6 @@
> >
确认选择 确认选择
</button> </button>
<view v-if="selectedItem" class="selected-info">
已选: {{ selectedItem.name }}
</view>
</view> </view>
</view> </view>
</template> </template>
@@ -277,7 +276,7 @@ onMounted(() => {
background-color: #f5f5f5; background-color: #f5f5f5;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/* padding: 20rpx; */ padding: 0rpx 40rpx;
} }
/* 搜索区域 */ /* 搜索区域 */
@@ -341,7 +340,7 @@ onMounted(() => {
/* 小区列表 */ /* 小区列表 */
.community-list { .community-list {
flex: 1; flex: 1;
padding: 0 30rpx; /* padding: 0 30rpx; */
margin-top: 20rpx; margin-top: 20rpx;
margin-bottom: 180rpx; margin-bottom: 180rpx;
@@ -495,11 +494,11 @@ onMounted(() => {
} }
.confirm-btn { .confirm-btn {
background-color: #007AFF; background-color: #4D5DFA;
color: #fff; color: #fff;
height: 100rpx; height: 100rpx;
line-height: 100rpx; line-height: 100rpx;
border-radius: 12rpx; border-radius: 8rpx;
font-size: 34rpx; font-size: 34rpx;
width: 100%; width: 100%;
margin: 0; margin: 0;

View File

@@ -0,0 +1,242 @@
<template>
<!-- 页面主容器 -->
<view class="door-open-page">
<view class="status_bar"></view>
<!-- 顶部标题栏 -->
<view class="top-bar">
<uni-nav-bar left-icon="left" title="远程开门" backgroundColor ="none" :border="false" @clickLeft="narBack" />
</view>
<view class="top-img">
<image src="/static/imgs/xuancheng-bg.png" mode="" style="width: 100%;height: 100%;"></image>
</view>
<!-- 提示文案 -->
<view class="tips-text">
远程开门
<text class="tips-desc">请选择要开的门并保持距离在10米以内,单次仅开一门</text>
</view>
<!-- 开门成功提示(默认隐藏) -->
<view v-if="showSuccess" class="success-toast">开门成功</view>
<!-- 开门授权区域 -->
<view class="auth-container">
<view class="auth-title">开门授权</view>
<!-- 网格布局:2行2列 -->
<view class="grid-container">
<!-- 每个门选项 -->
<view
v-for="(item, index) in doorList"
:key="index"
:class="['door-item', { active: selectedDoor === index }]"
@click="selectDoor(index)"
>
<!-- 图标占位(可替换为真实图标/图片) -->
<view class="door-icon">
<image class="icon-text" :src="selectedDoor === index ? item.iconActive:item.iconText"></image>
</view>
<!-- 门名称 -->
<text class="door-name">{{ item.name }}</text>
</view>
</view>
</view>
<!-- 确认开门按钮 -->
<button class="confirm-btn" @click="openDoor" :disabled="selectedDoor === -1">确认开门</button>
</view>
</template>
<script setup>
import { ref } from 'vue'
// 响应式数据:选中的门索引、开门成功提示
const selectedDoor = ref(-1) // -1表示未选中
const showSuccess = ref(false)
// 门列表数据()
const doorList = ref([
{ iconText: '/static/imgs/westDoor.png',iconActive:"/static/imgs/westDoorWhite.png", name: '西入门闸机' },
{ iconText: '/static/imgs/eastDoor.png',iconActive:"/static/imgs/eastDoorWhite.png", name: '东入门闸机' },
{ iconText: '/static/imgs/unitDoor.png',iconActive:"/static/imgs/unitDoorWhite.png", name: '1号楼1单元门' },
{ iconText: '/static/imgs/unitDoor.png',iconActive:"/static/imgs/unitDoorWhite.png", name: '1号楼2单元门' }
])
// 选择门的方法
const selectDoor = (index) => {
selectedDoor.value = index
showSuccess.value = false // 选择新门时隐藏成功提示
}
// 返回
const narBack = () =>{
uni.switchTab({
url:"/pages/index/index"
})
}
// 确认开门方法
const openDoor = () => {
// 校验:未选择门时提示
// if (selectedDoor.value === -1) {
// uni.showToast({
// title: '请先选择要开的门',
// icon: 'none'
// })
// return
// }
// 模拟开门接口请求(实际项目替换为真实接口)
setTimeout(() => {
showSuccess.value = true
// 可选:添加成功提示后自动隐藏
// setTimeout(() => showSuccess.value = false, 2000)
}, 500)
}
</script>
<style scoped>
/* 页面全局样式 */
.door-open-page {
background: linear-gradient(180deg, #E2F0FF, #F6FAFF);
min-height: 100vh;
padding: 30rpx 40rpx;
box-sizing: border-box;
}
.status_bar {
height: 32px;
width: 100%;
}
/* 顶部标题 */
.top-bar {
text-align: center;
margin-bottom: 20rpx;
}
.title-text {
font-size: 48rpx;
color: #ffffff;
font-weight: bold;
}
.top-img{
width: 460rpx;
height: 290rpx;
float: right;
}
/* 提示文案 */
.tips-text {
color: #000;
margin-bottom: 30rpx;
font-size: 68rpx;
line-height: 1.5;
font-weight: 700;
font-style: italic;
clear: both;
position: relative;
top: -86px;
left: 0;
}
.tips-desc {
display: block;
font-size: 24rpx;
opacity: 0.9;
color: #666666;
}
/* 开门成功提示 */
.success-toast {
background: rgba(0, 0, 0, 0.7);
color: #ffffff;
padding: 10rpx 20rpx;
border-radius: 30rpx;
font-size: 24rpx;
position: absolute;
top: 200rpx;
left: 50%;
transform: translateX(-50%);
z-index: 99;
}
/* 开门授权容器 */
.auth-container {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx 20rpx;
margin-bottom: 60rpx;
position: relative;
top: -86px;
left: 0;
}
.auth-title {
text-align: center;
font-size: 32rpx;
color: #333333;
padding-bottom: 20rpx;
border-bottom: 1px solid #e5e5e5;
margin-bottom: 30rpx;
}
/* 网格布局 */
.grid-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30rpx;
}
/* 门选项样式 */
.door-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 30rpx 20rpx;
border-radius: 10rpx;
border: 2px solid #2F77FD;
background-color: #F6F9FE;
transition: all 0.3s;
}
/* 选中状态 */
.door-item.active {
/* border-color: #2c6bc7; */
background-color: #4D5DFA;
color: #ffffff;
}
.door-item.active .door-name {
color: #ffffff !important;
}
/* 门图标 */
.door-icon {
width: 80rpx;
height: 80rpx;
/* background-color: #f5f5f5; */
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 15rpx;
}
.icon-text {
/* font-size: 40rpx; */
width: 100%;
height: 100%;
}
/* 门名称 */
.door-name {
font-size: 26rpx;
color: #333333;
}
/* 确认按钮 */
.confirm-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #4D5DFA;
color: #fff;
font-size: 32rpx;
border-radius: 20rpx;
border: none;
}
.confirm-btn::after {
border: none; /* 去除uni-app默认按钮边框 */
}
</style>

View File

@@ -1,5 +1,12 @@
{ {
"pages": [ "pages": [
{
"path": "pages/guide/guide",
"style": {
"navigationBarTitleText": "引导页",
"navigationStyle": "custom"
}
},
{ {
"path": "pages/index/index", "path": "pages/index/index",
"style": { "style": {
@@ -17,7 +24,49 @@
], ],
"subPackages": [ "subPackages": [
{ {
"root": "pages/notice-list", "root": "pageSubPack/loginSub",
"pages": [
{
"path": "pwd-login",
"style": {
"navigationBarTitleText": "账号登录",
"navigationStyle": "custom"
}
},
{
"path": "login",
"style": {
"navigationBarTitleText": "手机号登录",
"navigationStyle": "custom"
}
},
{
"path": "register-new-user",
"style": {
"navigationBarTitleText": "注册新用户",
"navigationStyle": "custom"
}
},
{
"path": "forget-pwd",
"style": {
"navigationBarTitleText": "忘记密码",
"navigationStyle": "custom"
}
},
{
"path": "change-pwd",
"style": {
"navigationBarTitleText": "修改密码",
"navigationStyle": "custom"
}
}
]
},
{
"root": "pageSubPack/notice-list",
"pages": [ "pages": [
{ {
"path": "select-community", "path": "select-community",
@@ -28,9 +77,28 @@
{ {
"path": "notice-list", "path": "notice-list",
"style": { "style": {
"navigationBarTitleText": "通知" "navigationBarTitleText": "通知公告"
} }
} },
{
"path": "noticeListDetails",
"style": {
"navigationBarTitleText": "公告详情"
}
},
]
},
{
"root": "pageSubPack/oneClickOpen",
"pages": [
{
"path": "oneClickOpen",
"style": {
"navigationBarTitleText": "一键开门",
"navigationStyle": "custom"
}
}
] ]
} }
], ],

View File

@@ -0,0 +1,219 @@
<template>
<view class="guide-page">
<view class="status_bar"></view>
<!-- 引导内容滑动容器 -->
<swiper
class="guide-swiper"
:current="currentStep"
@change="handleSwiperChange"
:disable-scroll="false"
:indicator-dots="false"
:mode="mode"
>
<!-- 便捷服务 -->
<swiper-item>
<view class="guide-item">
<view class="guide-img">
<image src="/static/imgs/guide1.png" style="width: 100%;height: 100%;"></image>
</view>
<view style="height: 200rpx;width: 100%;"></view>
<view class="guide-title">精准服务</view>
<view class="guide-subtitle">需求深度洞察,资源优化配置</view>
</view>
</swiper-item>
<!-- 高效管理 -->
<swiper-item>
<view class="guide-item">
<view class="guide-img">
<image src="/static/imgs/guide2.png" style="width: 100%;height: 100%;"></image>
</view>
<view style="height: 200rpx;width: 100%;"></view>
<view class="guide-title">高效沟通</view>
<view class="guide-subtitle">反馈即时响应,流程无缝衔接</view>
</view>
</swiper-item>
<!--安全保障 -->
<swiper-item>
<view class="guide-item">
<view class="guide-img">
<image src="/static/imgs/guide3.png" style="width: 100%;height: 100%;"></image>
</view>
<view style="height: 200rpx;width: 100%;"></view>
<view class="guide-title">智能管理</view>
<view class="guide-subtitle">数据驱动决策,风险提前预警</view>
</view>
</swiper-item>
</swiper>
<!-- 进度点 -->
<view class="progress-dots">
<view class="dot" :class="{ active: currentStep === 0 }"></view>
<view class="dot" :class="{ active: currentStep === 1 }"></view>
<view class="dot" :class="{ active: currentStep === 2 }"></view>
</view>
<!-- 操作按钮 -->
<view class="guide-btn-wrap">
<button
class="guide-btn"
:class="{ primary: currentStep === 2 }"
@click="handleBtnClick"
>
{{ currentStep === 2 ? '立即体验' : '下一步' }}
</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
// ========== 响应式数据 ==========
// 当前引导步骤(0/1/2)
const currentStep = ref(0);
const mode = ref("default")
// ========== 方法 ==========
// 滑动swiper切换步骤
const handleSwiperChange = (e) => {
currentStep.value = e.detail.current;
};
// 点击按钮操作
const handleBtnClick = () => {
// 前两步:点击下一步切换
if (currentStep.value < 2) {
currentStep.value++;
} else {
// 立即体验,跳转首页
// 1. 记录引导已完成
uni.setStorageSync('hasShowGuide', true);
// 2. 跳转首页
uni.navigateTo({
url:"/pageSubPack/loginSub/pwd-login"
})
// uni.switchTab({
// url: '/pages/index/index'
// });
}
};
</script>
<style scoped>
/* 页面容器 */
.guide-page {
width: 100%;
height: 100vh;
/* background-color: #f8f9ff; */
background: linear-gradient(to bottom, #E2F0FF, #F6FAFF);
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 120rpx 40rpx 80rpx;
box-sizing: border-box;
}
.status_bar {
height: 115px;
width: 100%;
}
/* 滑动容器 */
.guide-swiper {
flex: 1;
width: 100%;
}
/* 单个引导项 */
.guide-item {
width: 360px;
height: 480px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.guide-img {
width: 77%;
height: 50%;
margin-bottom: 80rpx;
}
/* 引导标题 */
.guide-title {
font-size: 56rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
}
/* 引导副标题 */
.guide-subtitle {
font-size: 28rpx;
color: #666;
}
/* 进度点容器 */
.progress-dots {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 60rpx;
}
/* 单个进度点 */
.dot {
width: 16rpx;
height: 16rpx;
border-radius: 8rpx;
background-color: #ddd;
margin: 0 8rpx;
}
/* 激活的进度点 */
.dot.active {
background-color: #4D5DFA;
width: 40rpx;
}
/* 按钮容器 */
.guide-btn-wrap {
width: 80%;
}
/* 引导按钮 */
.guide-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
padding: 0;
margin: 0;
box-sizing: border-box;
}
/* 默认按钮样式(下一步) */
.guide-btn:not(.primary) {
background-color: #fff;
color: #4D5DFA;
border: 2rpx solid #4D5DFA;
}
/* 主按钮样式(立即体验) */
.guide-btn.primary {
background-color: #4D5DFA;
color: #fff;
border: none;
}
/* 按钮禁用态 */
.guide-btn:disabled {
background-color: #ccc;
color: #fff;
border: none;
}
</style>

View File

@@ -1,26 +1,438 @@
<template> <template>
<view class="container"> <view class="content-class">
<view class="nar-top">
<!-- 这里是状态栏占位 -->
<view class="status_bar"></view>
<!-- 导航栏 -->
<view class="nav-bar">
<text class="nav-title">首页</text>
</view>
<view class="tabbarClass">
<view class="header-container" @click="navigateToPage">
<!-- 左侧小区选择区域 -->
<view class="location-area">
<!-- 定位图标 -->
<view class="location-icon">
<image src="/static/imgs/adress.png" style="height: 100%;width: 100%;"></image>
</view>
<!-- 小区名称和下拉箭头 -->
<view class="community-selector">
<text class="community-name">{{ currentCommunity }}</text>
<view class="community-icon">
<image src="/static/imgs/三角形.png" style="height: 100%;width: 100%;"></image>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- banner -->
<view class="bannarClass">
<uni-swiper-dot class="uni-swiper-dot-box" @clickItem=clickItem :info="bannerList" :current="current"
:mode="mode">
<swiper class="swiper-box" :current="swiperDotIndex" :autoplay="true">
<swiper-item v-for="(item, index) in bannerList" :key="index">
<view class="swiper-item">
<image :src="item.img" style="width: 100%;height: 100%;"></image>
</view>
</swiper-item>
</swiper>
</uni-swiper-dot>
</view>
<!-- 巡检 -->
<view class="inspectionClass">
<!-- 消息通知栏 -->
<view class="noticeClass">
<!-- 左侧消息通知文字 -->
<view class="notice-left">
<image src="/static/imgs/notice.png" class="notice-icon"></image>
</view>
<!-- 右侧滚动公告 -->
<view class="notice-right" @click="getNoticeMore">
<uni-notice-bar :text="noticeText" type="line" :showIcon="false" show-get-more scrollable
color="#fff" background-color="transparent" moreColor="#fff" no-border>
</uni-notice-bar>
</view>
</view>
<!-- 巡检数据统计区 -->
<view class="statistics-wrap">
<view class="statistics-item">
<text class="num">25</text>
<text class="label">巡检点</text>
</view>
<view class="statistics-item">
<text class="num">5</text>
<text class="label">巡检点</text>
</view>
<view class="statistics-item">
<text class="num">25</text>
<text class="label">巡检点</text>
</view>
</view>
</view>
<!-- 网格布局功能 -->
<view class="function-container">
<!-- 第一行功能:故障上报、一键开门、工单大厅、我的工单 -->
<view class="function-row">
<view class="function-item">
<image class="item-icon" src="/static/imgs/fault-report.png" mode="aspectFit"></image>
<text class="item-text">故障上报</text>
</view>
<view class="function-item" @click="goToOneOpenDoor">
<image class="item-icon" src="/static/imgs/open.png" mode="aspectFit"></image>
<text class="item-text">一键开门</text>
</view>
<view class="function-item">
<image class="item-icon" src="/static/imgs/work-order.png" mode="aspectFit"></image>
<text class="item-text">工单大厅</text>
</view>
<view class="function-item">
<image class="item-icon" src="/static/imgs/my-work.png" mode="aspectFit"></image>
<text class="item-text">我的工单</text>
</view>
</view>
<!-- 2 -->
<view class="function-row" style="margin-top: 2px;">
<view class="function-item">
<image class="item-icon" src="/static/imgs/complaint.png" mode="aspectFit"></image>
<text class="item-text">投诉处理</text>
</view>
</view>
</view>
</view> </view>
</template> </template>
<script setup> <script setup>
import {ref} from "vue" import {
ref
} from 'vue'
import {
onLoad,
onUnload
} from '@dcloudio/uni-app'
// 列表数据状态
const current = ref(0)
const currentCommunity = ref('碧桂园小区')
const bannerList = ref([{
img: '/static/bannar1.png'
},
{
img: '/static/bannar2.png'
},
])
const noticeText = ref('公告公告本小区今天安装了摄像头')
// =======方法=======
// 跳转小区
const navigateToPage =() =>{
uni.navigateTo({
url:"/pageSubPack/notice-list/select-community"
})
}
// 跳转公告
const getNoticeMore = () => {
uni.navigateTo({
url: "/pageSubPack/notice-list/notice-list"
})
}
// 跳转一键开门
const goToOneOpenDoor = ()=>{
uni.navigateTo({
url: "/pageSubPack/oneClickOpen/oneClickOpen"
})
}
// 接收选择小区返回的参数
const slelctHomeData = (data) => {
// console.log('收到B页面返回的数据:', data)
currentCommunity.value = data.name
}
onLoad(() => {
// 绑定全局事件监听
uni.$on('confirm', slelctHomeData)
})
// 页面销毁时移除监听(防止内存泄漏)
onUnload(() => {
uni.$off('confirm', slelctHomeData)
})
// 方法逻辑
</script> </script>
<style scoped lang="scss">
.container{
padding: 0 12px;
// background-color: #f5f5f5;
min-height: 100vh;
}
<style lang="scss" scoped>
.content-class {
// margin: 0 20rpx;
// padding: 2rpx;
background-color: #f9f9f9;
padding-bottom: 100rpx;
box-sizing: border-box;
/* 保证padding不会撑大容器 */
min-height: 110vh;
overflow-y: auto;
.nar-top {
height: 400rpx;
width: 100%;
// background-color: #C0E4FB;
background: repeating-linear-gradient(to right, #EDF4FA, #C0E4FB);
position: absolute;
}
.status_bar {
height: 46px;
width: 100%;
}
/* 导航栏 */
.nav-bar {
height: 44px;
line-height: 44px;
text-align: center;
}
.nav-title {
font-size: 14px;
font-weight: 400;
}
.header-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4rpx 30rpx;
box-sizing: border-box;
// border-bottom: 1rpx solid #f0f0f0;
position: absolute;
top: 85px;
left: 0;
z-index: 9999;
}
// 左侧区域
.location-area {
display: flex;
align-items: center;
position: relative;
flex: 1;
min-width: 0;
.location-icon {
width: 45rpx;
height: 45rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
.community-selector {
display: flex;
align-items: center;
max-width: 300rpx;
cursor: pointer;
.community-name {
font-size: 26rpx;
font-weight: 500;
color: #000;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200rpx;
}
.community-icon {
width: 20rpx;
height: 20rpx;
margin-left: 20rpx;
margin-top: -22rpx;
}
}
}
.bannarClass {
position: relative;
top: 110px;
left: 0;
padding: 40rpx;
}
.swiper-box {
height: 300rpx;
border-radius: 16rpx;
}
.swiper-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 300rpx;
color: #fff;
}
.inspectionClass {
position: relative;
top: 107px;
left: 0;
margin: 0 40rpx;
// height: 236rpx;
background: linear-gradient(to right, #7B87FA, #4D5DFA);
border-radius: 25rpx;
box-sizing: border-box;
padding: 30rpx;
}
// 消息通知栏
.noticeClass {
display: flex;
align-items: center;
height: 80rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
padding: 6rpx 20rpx;
margin-bottom: 30rpx;
border: 1rpx solid rgba(255, 255, 255, 0.1);
}
// 左侧“消息通知”文字
.notice-left {
margin-right: 20rpx;
}
.notice-icon {
width: 48rpx;
height: 48rpx;
}
// 右侧公告滚动区
.notice-right {
flex: 1; // 占满剩余宽度
}
// 深度修改uni-notice-bar样式
::v-deep .uni-noticebar {
background-color: transparent !important;
height: 100% !important;
line-height: 80rpx !important;
padding: 0 !important;
margin: 0 !important;
}
// 公告文字样式
::v-deep .uni-noticebar__content {
font-size: 26rpx !important;
color: #fff !important;
padding: 0 !important;
}
// 右侧箭头按钮
::v-deep .uni-noticebar__more {
color: #fff !important;
font-size: 24rpx !important;
padding: 0 0 0 10rpx !important;
}
// 隐藏默认滚动动画的多余样式
::v-deep .uni-noticebar__content-wrapper--scrollable {
left: 0 !important;
width: calc(100% - 30rpx) !important; // 给箭头留空间
}
// 统计区布局
.statistics-wrap {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
}
// 统计项样式
.statistics-item {
text-align: center;
}
.statistics-item .num {
font-size: 36rpx;
font-weight: bold;
color: #fff;
display: block;
line-height: 1;
margin-bottom: 10rpx;
}
.statistics-item .label {
font-size: 24rpx;
color: #fff;
opacity: 0.9;
}
.function-container {
background-color: #ffffff;
border-radius: 8rpx;
padding: 30rpx 20rpx;
box-sizing: border-box;
position: relative;
top: 123px;
left: 0;
margin: 0 40rpx;
}
// 功能行布局
.function-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
}
.function-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 25%; // 一行4个,每个占25%
padding: 10rpx 0;
// 第二行只有1个,单独调整宽度
.function-row:nth-child(2) & {
width: 25%; // 和第一行图标宽度保持一致
}
}
// 功能图标样式
.item-icon {
width: 88rpx;
height: 88rpx;
margin-bottom: 10rpx;
}
// 功能文字样式
.item-text {
font-size: 24rpx;
color: #333333;
font-weight: 400;
text-align: center;
}
}
</style> </style>

View File

@@ -1,22 +1,217 @@
<template> <template>
<view class="container"> <view class="contentStyle" style="">
<view class="status-bar"></view>
<uni-nav-bar title="我的" backgroundColor="transparent" :border="false">
</uni-nav-bar>
<scroll-view class="page" scroll-y>
<view class="topBox">
<view class="name">
<image src="/static/imgs/touxiang.png" class="img" />
<span style="padding: 5rpx 5rpx;display: flex;flex-direction: column;line-height: 60rpx;">
<span style="font-size: 36rpx;font-weight: 600;">
{{loginName}}
</span>
<span style="font-size: 26rpx;color:#666666;">
{{phoneNum}}
</span>
</span>
</view>
</view>
<view class="contentBox">
<div class="page-container">
<view class="list-container">
<view class="list-item" v-for="(item, index) in list" :key="index" @click="onItemClick(item)">
<image :src="item.icon" class="item-icon" />
<view class="item-content">
<text class="item-title">{{ item.title }}</text>
<span class="item-desc" v-if="item.desc"><span
style="align-self: flex-end;font-size: 24rpx;vertical-align: baseline;">{{ item.desc }}</span></span>
</view>
<view class="item-arrow"></view>
<!-- <image src="http://47.104.199.163:9090/images/propertyImgs/arrow-right.png" class="arrow-icon" /> -->
</view>
</view>
</div>
</view>
</scroll-view>
</view> </view>
</template>
</template>
<script setup> <script setup>
import {
ref
} from 'vue';
const list = ref([{
icon: "/static/imgs/shangbao.png",
title: "上报记录"
},
{
icon: "/static/imgs/xunjian.png",
title: "巡检记录",
// desc: "您可以在这里添加家人、朋友、租客~"
},
{
icon: "http://47.104.199.163:9090/images/propertyImgs/setting.png",
title: "设置"
}
])
const loginName = ref("wangh")
const phoneNum = ref('156*****1212')
// --方法----
const goToDetail = () => {
uni.navigateTo({
url: '/pageSubPack/notice-list/noticeListDetails'
})
}
// 返回
const onItemClick = (item) => {
if (item.title == '上报记录') {
uni.redirectTo({
url: '',
});
} else if (item.title == '巡检记录') {
uni.redirectTo({
url: '',
});
} else if (item.title == '设置') {
uni.redirectTo({
url: '',
});
}
}
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.container{ .contentStyle {
// padding: 0 12px; display: flex;
// background-color: #f5f5f5; flex-direction: column;
min-height: 100vh; min-height: 100vh;
background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%)
}
.topBox {
width: 100%;
padding: 20rpx 0px;
position: relative;
// background-color: #1A75FF;
}
.titleBox {
width: 100%;
position: relative;
.title {
font-size: 36rpx;
line-height: 80rpx;
text-align: center;
font-weight: 500;
}
.settingImg {
width: 48rpx;
height: 48rpx;
top: 16rpx;
right: 0px;
}
}
.img {
width: 60px;
height: 60px;
margin-right: 10rpx;
border-radius: 50%;
}
.name {
bottom: 30rpx;
left: 30rpx;
display: flex;
align-items: center;
}
.contentBox {
position: relative;
}
.status-bar {
width: 100vw;
height: 46px;
}
.list-item {
display: flex;
align-items: center;
/* background: #fff; */
padding: 20rpx 6rpx;
border-radius: 8rpx;
/* border-bottom:2rpx solid #f2f2f2; */
/* margin-bottom: 10rpx; */
}
.item-icon {
width: 84rpx;
height: 84rpx;
/* margin-right: 20rpx; */
}
.item-content {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
}
.item-title {
font-size: 32rpx;
/* color: #333; */
font-weight: 400;
margin: 10rpx 20rpx;
/* margin-bottom: 6rpx; */
}
.item-desc {
font-size: 24rpx;
color: #b5b5b5;
white-space: nowrap;
/* 强制不换行 */
overflow: hidden;
/* 超出隐藏 */
text-overflow: ellipsis;
}
.item-arrow {
width: 16rpx;
height: 16rpx;
border-right: 3rpx solid #999999;
border-bottom: 3rpx solid #999999;
transform: rotate(-45deg);
margin-right: 10rpx;
}
.page {
flex: 1;
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
} }
</style> </style>

View File

@@ -1,153 +0,0 @@
<template>
<view class="order-notice-page">
<!-- 列表 -->
<scroll-view class="order-list" scroll-y>
<!-- 单个工单项 -->
<view
class="order-item"
v-for="(item, index) in orderList"
:key="index"
@click="goToDetail(item.id)"
>
<!-- 工单标题栏(含未读红点) -->
<view class="order-header">
<text class="order-title">你有新的维修工单</text>
<view class="unread-dot" v-if="item.unread"></view>
</view>
<!-- 工单内容 -->
<view class="order-content">
<text class="content-title">{{ item.title }}</text>
<text class="content-desc">{{ item.desc }}</text>
</view>
<view class="line-class"></view>
<!-- 详情入口 -->
<view class="order-detail">
<text class="detail-text">详情</text>
<uni-icons type="right" size="14" color="#999"></uni-icons>
</view>
</view>
<!-- 空数据占位 -->
<view class="empty-tip" v-if="orderList.length === 0">
<text>暂无新的维修工单</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue'
// 模拟工单数据(可替换为接口请求)
const orderList = ref([
{
id: 1,
unread: true, // 是否未读(显示红点)
title: '标题巴拉巴拉',
desc: '1栋楼下道路路灯不亮,并且有的亮度不够,请物业公司及时更换,防止发生意外事故。'
},
{
id: 2,
unread: false, // 已读(无红点)
title: '标题巴拉巴拉',
desc: '1栋楼下道路路灯不亮,并且有的亮度不够,请物业公司及时更换,防止发生意外事故。'
}
])
// 点击跳转工单详情页
const goToDetail = (orderId) => {
uni.navigateTo({
url: `/pages/repair-detail/repair-detail?id=${orderId}` // 替换为你的详情页路径
})
}
</script>
<style scoped lang="scss">
.order-notice-page {
min-height: 100vh;
background-color: #f5f5f5;
}
// 工单列表
.order-list {
padding: 10px 0px;
height: calc(100vh - 44px);
// 单个工单项
.order-item {
background-color: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
position: relative;
// 工单标题栏(含未读红点)
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.order-title {
font-size: 15px;
color: #333;
font-weight: 500;
}
// 未读红点
.unread-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #ff3b30;
}
}
// 工单内容
.order-content {
margin-bottom: 12px;
.content-title {
font-size: 14px;
color: #666;
display: block;
margin-bottom: 6px;
}
.content-desc {
font-size: 13px;
color: #999;
line-height: 1.5;
display: block;
}
}
.line-class{
border: 1px solid #d4d4d4;
}
// 详情入口
.order-detail {
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: 20rpx;
.detail-text {
font-size: 13px;
color: #999;
margin-right: 4px;
}
}
}
// 空数据提示
.empty-tip {
text-align: center;
font-size: 14px;
color: #999;
margin-top: 50px;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB