2025-9-25学习笔记

目录

微信登录(续):

功能实现(续):

业务层:

        构建者设计模式:

测试效果展示:

校验token:

微实战:

护理项目接口:

分页查询护理项目列表:

根据编号查询护理项目信息:

参观预约接口:

查询当天取消预约数量:

查询每个时段剩余预约次数:

新增预约:

分页查询预约:

取消预约:


微信登录(续):

功能实现(续):

业务层:

        新增WechatService接口:

package com.zzyl.nursing.service;

public interface WechatService {

    /**
     * 获取openid
     * @param code
     * @return
     */
    public String getOpenid(String code);

    /**
     * 获取手机号
     * @param detailCode
     * @return
     */
    public String getPhone(String detailCode);
}

        新增WechatServiceImpl实现类:

package com.zzyl.nursing.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zzyl.nursing.service.WechatService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class WechatServiceImpl implements WechatService {


    // 登录
    private static final String REQUEST_URL = "https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code";

    // 获取token
    private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";

    // 获取手机号
    private static final String PHONE_REQUEST_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=";


    @Value("${wechat.appId}")
    private String appid;

    @Value("${wechat.appSecret}")
    private String secret;


    /**
     * 获取openid
     * @param code
     * @return
     */
    @Override
    public String getOpenid(String code) {

        // 获取公共参数
        Map<String,Object> paramMap = getAppConfig();
        paramMap.put("js_code", code);

        String result = HttpUtil.get(REQUEST_URL, paramMap);
        // 结果是一个map
        JSONObject jsonObject = JSONUtil.parseObj(result);
        // 判断接口响应是否出错
        if(ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))) {
            throw new RuntimeException(jsonObject.getStr("errmsg"));
        }

        String openid = jsonObject.getStr("openid");

        return openid;
    }

    /**
     * 封装公共参数
     * @return
     */
    private Map<String, Object> getAppConfig() {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("appid", appid);
        paramMap.put("secret", secret);
        return paramMap;
    }

    /**
     * 获取手机号
     * @param detailCode
     * @return
     */
    @Override
    public String getPhone(String detailCode) {

        String token = getToken();
        String url = PHONE_REQUEST_URL + token;
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("code", detailCode);
        // 发起请求
        String result = HttpUtil.post(url, JSONUtil.toJsonStr(paramMap));
        // 是一个map
        JSONObject jsonObject = JSONUtil.parseObj(result);
        // 判断接口响应是否出错
        if(jsonObject.getInt("errcode") != 0) {
            throw new RuntimeException(jsonObject.getStr("errmsg"));
        }

        return jsonObject.getJSONObject("phone_info").getStr("phoneNumber");
    }

    /**
     * 获取token
     * @return
     */
    private String getToken() {

        Map<String, Object> paramMap = getAppConfig();
        // 发起请求
        String result = HttpUtil.get(TOKEN_URL, paramMap);
        // 是一个map
        JSONObject jsonObject = JSONUtil.parseObj(result);
        // 判断接口响应是否出错
        if(ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))) {
            throw new RuntimeException(jsonObject.getStr("errmsg"));
        }

        String token = jsonObject.getStr("access_token");

        return token;

    }
}

        在application-dev.yml中配置appidyijiappsecret:

#WECHAT配置
wechat:
  appId: ***
  appSecret: *****

        在IFamilyMemberService中定义login方法:

/**
 * 微信登录
 * @param userLoginRequestDto
 * @return
 */
LoginVo login(UserLoginRequestDto userLoginRequestDto);

        实现方法:

@Autowired
private WechatService wechatService;

@Autowired
private TokenService tokenService;

static List<String> DEFAULT_NICKNAME_PREFIX = ListUtil.of(
        "生活更美好",
        "大桔大利",
        "日富一日",
        "好柿开花",
        "柿柿如意",
        "一椰暴富",
        "大柚所为",
        "杨梅吐气",
        "天生荔枝"
);

/**
 * 小程序端登录
 * @param userLoginRequestDto
 * @return
 */
@Override
public LoginVo login(UserLoginRequestDto userLoginRequestDto) {
    // 1.调用微信api,根据code获取openId
    String openId = wechatService.getOpenid(userLoginRequestDto.getCode());

    // 2.根据openId查询用户
    FamilyMember familyMember = getOne(Wrappers.<FamilyMember>lambdaQuery(FamilyMember.class)
            .eq(FamilyMember::getOpenId, openId));

    // 3.如果用户为空,则将openId设置到对象中
    if (ObjectUtil.isEmpty(familyMember)) {
        familyMember = FamilyMember.builder().openId(openId).build();
    }

    // 4.调用微信api获取用户绑定的手机号
    String phone = wechatService.getPhone(userLoginRequestDto.getPhoneCode());
    familyMember.setPhone(phone);

    // 5.保存或修改用户
    saveOrUpdateFamilyMember(familyMember, phone);

    // 6.将用户id存入token,返回
    Map<String, Object> claims = new HashMap<>();
    claims.put("userId", familyMember.getId());
    claims.put("nickName", familyMember.getName());

    String token = tokenService.createToken(claims);
    LoginVo loginVo = new LoginVo();
    loginVo.setToken(token);
    loginVo.setNickName(familyMember.getName());
    return loginVo;
}

/**
 * 保存或修改客户
 * @param member
 * @param phone
 */
private void saveOrUpdateFamilyMember(FamilyMember member, String phone) {

    // 1.判断取到的手机号与数据库中保存的手机号不一样
    if(ObjectUtil.notEqual(phone, member.getPhone())) {
        // 修改手机号
        member.setPhone(phone);
    }
    // 2.判断id存在,则修改
    if (ObjectUtil.isNotEmpty(member.getId())) {
        updateById(member);
        return;
    }
    // 3.id不存在,保存新用户
    // 随机组装昵称,词组+手机号后四位
    String nickName = DEFAULT_NICKNAME_PREFIX.get((int) (Math.random() * DEFAULT_NICKNAME_PREFIX.size()))
            + StringUtils.substring(member.getPhone(), 7);

    member.setName(nickName);
    save(member);
}

        由于这里用到了构建者设计模式,所以还需要到FamilyMember类当中加上@Builder注解

        构建者设计模式:

        当我们在一个实体类中添加了@Builder注解后,这个类就支持了构建者模式来使用这个类,我们可以查看生成后的class文件,以下代码是生成后的核心代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.zzyl.entity;

import com.zzyl.base.BaseEntity;

public class Member extends BaseEntity {
    private String phone;
    private String name;
    private String avatar;
    private String openId;
    private int gender;
    
    // 省略了get 和  set方法

    public static MemberBuilder builder() {
        return new MemberBuilder();
    }

    public Member(final String phone, final String name, final String avatar, final String openId, final int gender) {
        this.phone = phone;
        this.name = name;
        this.avatar = avatar;
        this.openId = openId;
        this.gender = gender;
    }

    public Member() {
    }

    public static class MemberBuilder {
        private String phone;
        private String name;
        private String avatar;
        private String openId;
        private int gender;

        MemberBuilder() {
        }

        public MemberBuilder phone(final String phone) {
            this.phone = phone;
            return this;
        }

        public MemberBuilder name(final String name) {
            this.name = name;
            return this;
        }

        public MemberBuilder avatar(final String avatar) {
            this.avatar = avatar;
            return this;
        }

        public MemberBuilder openId(final String openId) {
            this.openId = openId;
            return this;
        }

        public MemberBuilder gender(final int gender) {
            this.gender = gender;
            return this;
        }

        public Member build() {
            return new Member(this.phone, this.name, this.avatar, this.openId, this.gender);
        }

        public String toString() {
            return "Member.MemberBuilder(phone=" + this.phone + ", name=" + this.name + ", avatar=" + this.avatar + ", openId=" + this.openId + ", gender=" + this.gender + ")";
        }
    }
}

测试效果展示:

        查看数据库数据发现create_by、update_by字段自动填充(这个1是来自后端的admin用户,微信小程序这里不需要自动填充这两个字段)

        修改MyMetaObjectHandler代码如下:

//导包略...

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Autowired
    private HttpServletRequest request;

    public boolean isExclude() {
        String requestURI = request.getRequestURI();
        if(requestURI.startsWith("/member")) {
            return true;
        }
        return false;
    }


    @Override
    public void insertFill(MetaObject metaObject) {
        if(!isExclude()) {
            this.strictInsertFill(metaObject, "createBy", String.class, String.valueOf(getLoginUserId()));
        }
        this.strictInsertFill(metaObject, "createTime", Date.class, DateUtils.getNowDate());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
        if(!isExclude()) {
            this.setFieldValByName("updateBy", String.valueOf(getLoginUserId()), metaObject);
        }
    }
    //略......
}

校验token:

        项目中通过ThreadLocal类获取token并储存在线程当中,方便后面的校验token

        common模块下utils包-UserThreadLocal:

package com.zzyl.common.utils;

import lombok.extern.slf4j.Slf4j;

/**
 * subjectContent.java
 *  用户主体对象
 */
@Slf4j
public class UserThreadLocal {

    private static final ThreadLocal<Long> LOCAL = new ThreadLocal<>();

    private UserThreadLocal() {

    }

    /**
     * 将authUserInfo放到ThreadLocal中
     *
     * @param authUserInfo {@link Long}
     */
    public static void set(Long authUserInfo) {
        LOCAL.set(authUserInfo);
    }

    /**
     * 从ThreadLocal中获取authUserInfo
     */
    public static Long get() {
        return LOCAL.get();
    }

    /**
     * 从当前线程中删除authUserInfo
     */
    public static void remove() {
        LOCAL.remove();
    }

    /**
     * 从当前线程中获取前端用户id
     * @return 用户id
     */
    public static Long getUserId() {
        return LOCAL.get();
    }


}

        MemberInterceptor:

package com.zzyl.framework.interceptor;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zzyl.common.exception.base.BaseException;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.common.utils.UserThreadLocal;
import com.zzyl.framework.web.service.TokenService;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MemberInterceptor implements HandlerInterceptor {
    private final TokenService tokenService;

    public MemberInterceptor(TokenService tokenService) {
        this.tokenService = tokenService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 如果不是Controller层的请求,直接放行
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        // 获取header中的token
        String token = request.getHeader("authorization");

        // 如果token为空,响应401,重新登录
        if (StringUtils.isEmpty(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            throw new BaseException("认证失败");
        }

        // 解析token
        Claims claims = tokenService.parseToken(token);
        if (ObjectUtil.isEmpty(claims)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            throw new BaseException("认证失败");
        }

        // 获取token中的数据
        Long userId = MapUtil.get(claims, "userId", Long.class);
        if (ObjectUtil.isEmpty(userId)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            throw new BaseException("认证失败");
        }

        // 将用户id放入ThreadLocal中
        UserThreadLocal.set(userId);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserThreadLocal.remove();
    }
}

        注册拦截器:注入MemberInterceptor对象并且编写自定义拦截规则:

        registry.addInterceptor(memberInterceptor).excludePathPatterns("/member/user/login","/member/roomTypes") .addPathPatterns("/member/**");

微实战:

        通过接口文档自主完成剩余接口开发:

护理项目接口:

分页查询护理项目列表:

        MemberOrderController:

    @Autowired
    private INursingProjectService nursingProjectService;

    @GetMapping("/page")
    @ApiOperation("分页查询护理项目列表")
    public TableDataInfo<NursingProject> page(Integer pageNum, Integer pageSize, String name, Integer status) {
            return nursingProjectService.pageByNameAndStatus(pageNum, pageSize, name, status);
    }

        NursingProjectServiceImpl:

/**
     * 分页查询护理项目(小程序端)
     * @param pageNum 当前页码
     * @param pageSize  每页显示记录数
     * @param name  护理项目名称
     * @param status    状态
     * @return  分页数据
     */
    @Override
    public TableDataInfo<NursingProject> pageByNameAndStatus(Integer pageNum, Integer pageSize, String name, Integer status) {
        Page<NursingProject> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<NursingProject> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(name != null, NursingProject::getName, name)
                .eq(status != null, NursingProject::getStatus, status);
        page = page(page, queryWrapper);

        TableDataInfo<NursingProject> tableDataInfo = buildTableData(page);
        return tableDataInfo;
    }

    private static TableDataInfo<NursingProject> buildTableData(Page<NursingProject> page) {
        TableDataInfo<NursingProject> tableDataInfo = new TableDataInfo<>();
        tableDataInfo.setRows(page.getRecords());
        tableDataInfo.setTotal(page.getTotal());
        tableDataInfo.setCode(200);
        tableDataInfo.setMsg("查询成功");
        return tableDataInfo;
    }

根据编号查询护理项目信息:

        MemberOrderController:

    @Autowired
    private INursingProjectService nursingProjectService;

    @GetMapping("/{id}")
    @ApiOperation("获取护理项目详细信息")
    public R<NursingProject> getInfo(@ApiParam(value = "护理项目ID", required = true)
                                     @PathVariable Long id) {
        return R.ok(nursingProjectService.selectNursingProjectById(id));
    }

        NursingProjectServiceImpl:

    /**
     * 查询护理项目
     * 
     * @param id 护理项目主键
     * @return 护理项目
     */
    @Override
    public NursingProject selectNursingProjectById(Long id)
    {
        return getById(id);
    }

参观预约接口:

查询当天取消预约数量:

        这个接口是为了防止有人恶意预约并取消,当取消超过三次后当天禁止预约

        MemberReservationController:

    @Autowired
    private IReservationService reservationService;

    @GetMapping("/cancelled-count")
    @ApiOperation("查询取消预约数量")
    public R<Integer> getCancelledReservationCount() {
        Long userId = UserThreadLocal.getUserId();
        int count = reservationService.getCancelledCount(userId);
        return R.ok(count);
    }

        ReservationServiceImpl:

    /**
     * 查询取消预约次数
     *
     * @param userId    用户id
     * @return  取消预约次数
     */
    @Override
    public int getCancelledCount(Long userId) {
        // 2024/11/11 00:00:00
        LocalDateTime startTime = LocalDate.now().atStartOfDay();
        // 2024/10/12 00:00:00
        LocalDateTime endTime = startTime.plusDays(1);

        LambdaQueryWrapper<Reservation> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Reservation::getUpdateBy, userId)
                .eq(Reservation::getStatus, 2)
                .between(Reservation::getUpdateTime, startTime, endTime);

        return (int) count(lambdaQueryWrapper);
    }

查询每个时段剩余预约次数:

        MemberReservationController:

    @Autowired
    private IReservationService reservationService;

    @GetMapping("/countByTime")
    @ApiOperation("查询每个时间段剩余预约次数")
    public R<List<TimeCountVo>> countReservationsForEachTimeWithinTimeRange(Long time) {
        List<TimeCountVo> list = reservationService.countReservationsForTime(time);
        return R.ok(list);
    }

        ReservationServiceImpl:

    @Autowired
    private ReservationMapper reservationMapper;

    /**
     * 查询每个时间段剩余预约次数
     *
     * @return 结果
     */
    @Override
    public List<TimeCountVo> countReservationsForTime(Long time) {
        LocalDateTime localDateTime = LocalDateTimeUtil.of(time);

        LocalDateTime startTime = localDateTime.toLocalDate().atStartOfDay();

        LocalDateTime endTime = startTime.plusDays(1);
        List<TimeCountVo> timeCountVoList = reservationMapper.countReservationsForTime(startTime, endTime);
        return timeCountVoList;
    }

        ReservationMapper.xml:

    <select id="countReservationsForTime" resultType="com.zzyl.nursing.vo.TimeCountVo">
        SELECT time, 6 - COUNT(*) AS count
        FROM reservation
        WHERE `time` BETWEEN #{startTime}
          AND #{endTime}
          and status != 2
        GROUP BY `time`
    </select>

新增预约:

        MemberReservationController:

    @Autowired
    private IReservationService reservationService;

    /**
     * 新增预约信息
     */
    @PostMapping
    @ApiOperation("新增预约信息")
    public AjaxResult add(@RequestBody ReservationDto reservationDto) {
        return toAjax(reservationService.insertReservation(reservationDto));
    }

        ReservationServiceImpl:

/**
     * 新增预约信息
     *
     * @param reservationDto 预约信息
     * @return 结果
     */
    @Override
    public int insertReservation(ReservationDto reservationDto) {
        Long userId = UserThreadLocal.getUserId();
        Reservation reservation = BeanUtil.toBean(reservationDto, Reservation.class);
        reservation.setCreateBy(String.valueOf(userId));
        reservation.setUpdateBy(String.valueOf(userId));
        reservation.setStatus(0);
        try {
            return save(reservation) ? 1 : 0;
        } catch (Exception e) {
            throw new BaseException("不能重复预约哦");
        }
    }

分页查询预约:

        MemberReservationController:

    @Autowired
    private IReservationService reservationService;

    /*
     *分页查询增加预约人姓名,手机号,状态,类型的查询条件
     */
    @GetMapping("/page")
    @ApiOperation("分页查询预约")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "pageNum", value = "页码", required = true, dataType = "int", paramType = "query"),
            @ApiImplicitParam(name = "pageSize", value = "每页数量", required = true, dataType = "int", paramType = "query"),
            @ApiImplicitParam(name = "status", value = "状态", dataType = "int", paramType = "query"),
    })
    public R<TableDataInfo<Reservation>> findByPage(@RequestParam(defaultValue = "1") int pageNum,
                                                    @RequestParam(defaultValue = "10") int pageSize,
                                                    @RequestParam(required = false) Integer status) {
        TableDataInfo<Reservation> tableDataInfo = reservationService.selectByPage(pageNum, pageSize, status);
        return R.ok(tableDataInfo);
    }

        ReservationServiceImpl:

    /**
     * 分页查询预约信息
     *
     * @param pageNum
     * @param pageSize
     * @param status
     * @return
     */
    @Override
    public TableDataInfo<Reservation> selectByPage(int pageNum, int pageSize, Integer status) {
        Long userId = UserThreadLocal.getUserId();
        if (ObjectUtil.isEmpty(userId)) {
            throw new BaseException("请先登录");
        }
        LambdaQueryWrapper<Reservation> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Reservation::getCreateBy, userId);
        lambdaQueryWrapper.eq(ObjectUtil.isNotEmpty(status), Reservation::getStatus, status);
        lambdaQueryWrapper.orderByDesc(Reservation::getCreateTime);
        Page<Reservation> page = new Page<>(pageNum, pageSize);
        page = page(page, lambdaQueryWrapper);

        return createTableDataInfo(page);
    }

    private TableDataInfo<Reservation> createTableDataInfo(Page<Reservation> page) {
        TableDataInfo<Reservation> tableDataInfo = new TableDataInfo<>();
        tableDataInfo.setRows(page.getRecords());
        tableDataInfo.setTotal(page.getTotal());
        tableDataInfo.setMsg("请求成功");
        tableDataInfo.setCode(200);
        return tableDataInfo;
    }

取消预约:

        MemberReservationController:

    @Autowired
    private IReservationService reservationService;

    /**
     * 取消预约
     */
    @PutMapping("/{id}/cancel")
    @ApiOperation("取消预约")
    public AjaxResult cancel(@PathVariable Long id) {
        return toAjax(reservationService.cancelReservation(id));
    }

        ReservationServiceImpl:

    /**
     * 取消预约
     *
     * @param id
     * @return
     */
    @Override
    public int cancelReservation(Long id) {
        Reservation reservation = getById(id);
        if (ObjectUtil.isEmpty(reservation)) {
            throw new BaseException("预约不存在");
        }
        reservation.setStatus(2);
        reservation.setUpdateBy(String.valueOf(UserThreadLocal.getUserId()));
        return updateById(reservation) ? 1 : 0;
    }

将sx2_blog.html sx2_photo.html sx2_video.html修改为三列排版方式,对应css文件也请更新 增加左导向栏和右导向栏,宽度均为150px,背景色为#FAFAF0,导向链接以颜色为#E8E7D0的分隔线相隔;鼠标指针移动到链接上时,背景色变为白色 主要内容区在中间。左侧是网页导向栏,右侧是更新记录栏,要求横向排版,目录在上方 将菜单栏修改为简单导向菜单。这个是要求,下面给你第一段代码。<!DOCTYPE HTML> <html> <head> <title>我的日记</title> <meta charset="UTF-8"/> <link href="sx2_1.css" rel="stylesheet"> </head> <body id="blog"> <header> <h1>我的网页</h1> </header> <nav> <ul> <li><a href="sx2_photo.html" id="photo">我的照片</a></li> <li><a href="sx2_video.html" id="video">我的录像</a></li> <li><a href="sx2_blog.html" id="blog">我的日记</a></li> </ul> </nav> <section> <aside class="aside aside-left"> <h3>网页导向</h3> <a href="sx2_blog.html">日记首页</a> <a href="#personal">个人日记</a> <a href="#travel">旅行日记</a> <a href="#work">工作日记</a> <a href="#study">学习日记</a> <a href="#life">生活日记</a> </aside> <div class="main"> <h2>我的日记</h2> <p> 2025年10月25日 星期五 晴<br><br> 今天是个美好的日子,阳光明媚,心情格外舒畅。<br> 早上起床后,我决定记录下这美好的一天。<br> 王中王<br> .....................<br> 这些文字记录了我成长的足迹,每一篇日记都是珍贵的回忆。 </p> <h3>最新日记</h3> <p>• 2025-10-24 周末旅行记录</p> <p>• 2025-10-23 工作心得分享</p> <p>• 2025-10-22 读书笔记更新</p> </div> <aside class="aside aside-right"> <h3>更新记录</h3> <a href="#">2025-10-25 新增日记</a> <a href="#">2025-10-24 更新分类</a> <a href="#">2025-10-23 优化界面</a> <a href="#">2025-10-22 添加功能</a> <a href="#">2025-10-21 修复问题</a> <a href="#">2025-10-20 系统升级</a> </aside> </section> <footer> <a href="mailto:abc@efg.com">请您留言</a> <span>更新日期:2025年10月25日</span> </footer> </body> </html>
11-20
要将 `sx2_blog.html`、`sx2_photo.html`、`sx2_video.html` 修改为三列排版,同时更新对应的 CSS 文件,可以按照以下步骤进行操作: ### HTML 结构调整 首先,需要确保 HTML 文件中有合适的结构来容纳左导向栏、主要内容区和右导向栏。以下是一个示例 HTML 结构: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="styles.css"> <title>Document</title> </head> <body> <!-- 导航栏 --> <nav> <ul> <li><a href="sx2_blog.html">博客</a></li> <li><a href="sx2_photo.html">照片</a></li> <li><a href="sx2_video.html">视频</a></li> </ul> </nav> <!-- 主要内容区域 --> <section> <!-- 左导向栏 --> <div class="aside aside-left"> <a href="#">链接1</a> <a href="#">链接2</a> <a href="#">链接3</a> </div> <!-- 主要内容 --> <div class="main"> <h2>主要内容</h2> <p>这里是主要内容区域。</p> </div> <!-- 右导向栏 --> <div class="aside aside-right"> <a href="#">更新记录1</a> <a href="#">更新记录2</a> <a href="#">更新记录3</a> </div> </section> <!-- 页脚 --> <footer> <p>版权所有 © 2024</p> </footer> </body> </html> ``` ### CSS 文件更新 接下来,更新 CSS 文件以实现所需的布局和样式: ```css * { margin: 0; padding: 0; border: 0; font-size: 100%; box-sizing: border-box; } body { background-color: #f2f2e1; } a { color: #e58712; text-decoration: none; } a:hover { color: #9bbb38; } nav ul { margin: 0; padding: 0; background: #9bbb38; display: flex; list-style: none; } nav li { margin: 0; } nav li a { display: block; padding: 10px 20px; color: #ffffff; text-align: center; text-decoration: none; } nav li a:hover { background: #fff; color: #9BBB38; } section { display: flex; flex-flow: row wrap; } section > * { padding: 10px; } .aside { flex: 0 0 150px; background: #fafaf0; } .main { flex: 1 0; } .aside-left { order: 1; } .main { order: 2; } .aside-right { order: 3; } .aside a { display: block; padding: 10px; border-bottom: 1px solid #e8e7d0; } .aside a:hover { background: #ffffff; } footer { border-top: 10px solid #9bbb38; font-size: .8em; text-align: right; padding: 10px; } ``` ### 代码解释 - **HTML 结构**: - 使用 `<nav>` 元素创建导航栏,包含博客、照片和视频的链接。 - `<section>` 元素用于容纳三列布局,包含左导向栏、主要内容区和右导向栏。 - 每个导向栏使用 `<div>` 元素,并添加相应的类名(`aside`、`aside-left`、`aside-right`)。 - 主要内容区使用 `<div class="main">` 元素。 - **CSS 样式**: - 使用 `flexbox` 布局实现三列排版。 - 左导向栏和右导向栏的宽度设置为 150px,背景色为 `#FAFAF0`。 - 导向链接使用 `border-bottom` 属性添加 `#E8E7D0` 分隔线。 - 鼠标悬停在链接上时,背景色变为白色。 - 导航栏使用 `flexbox` 布局,实现横向排版。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值