了解Web项目前后端分离开发流程,这一篇就够了

MultiBallSquare 产品开发文档
一、产品简介
1.产品目的
本产品为MultiBallSquare —— 花旗ESG评估平台的网站,目标:让投资者快速、清晰、全面了解到亚洲ESG全景;并获取有建设性的ESG投资建议和相关支撑数据。
2.产品功能

  1. 为投资者提供亚洲esg图景(基本政策动态,集中的行业实时动态,符合亚洲实际情况的esg指标框架);
  2. 为投资者提供客观的esg数据库,便于其灵活考察关注的数据。
  3. 以行业为单位为投资者提供有效的esg评估方案和esg评分排名,并实时监控esg指标权重的更新。
  4. 提供评估公司详细的esg评估文档。
  5. 可拓展但未开发业务构思:实地数据调研&采集【第三方合作外包业务或者自身提供专业团队】、负面信息监控&处理、ESG个性化定制服务。
    二、开发流程
    1.开发流程
  6. 需求分析,撰写产品需求文档
  7. 技术选型
  8. 书写接口文档
  9. 数据库设计
  10. 导入jar包
  11. 编码开发
  12. 测试
  13. 运维
    2.技术选型
    产品分前后端分离开发+算法端计算指标权重。
    前端分为web和android端。
  14. 前端:HTML,CSS,JavaScript,Vue,ElementUI,Echart开发。【开发工具:Webstorm】
  15. 后端:Springboot+Mybatis。【开发工具:IDEA,Navicat,PowerDesigner,Postman】
  16. 算法:神经网络模型——多层感知机,调用MLP库函数。【开发工具:Pycharm】
  17. 服务器:
  18. 服务器:阿里云服务器ECS
  19. 服务器操作系统:CentOS 8.5 64位
  20. 公网IP:47.113.230.20
  21. 数据库:MySQL
    三、文档说明
  22. 本文档为实际开发过程产品细节设计文档,参考产品需求文档,撰写实际开发产品时所使用的的数据类型、函数逻辑、界面设计。
  23. 阅读撰写文档的人员包括:前端(web,app)、后端、算法开发人员。
    四、产品开发内容
    1.前端
    1.1.界面总体设计
    MultiBallSquare采用VUE+Element+Echarts进行前端开发,布局采用导航栏在左,winth=180px,标题栏在右上,信息展示页面在右下。导航栏分首页、ESG简介、ESG数据、ESG信息来源、ESG产品与咨询、个人中心和管理员登录7个模块。标题栏有登录,联系我们,帮助,全局搜索,个人信息几个标签。点击导航栏对应子模块,在信息页面展示相应内容。信息页面总体采用栅栏布局,元素定位方式主要采用相对定位margin,padding。

1.2.首页设计
首页信息展示页面共6部分:

<div name="div1" id="div1" ref="div1">
<div style="margin-top: 50px" name="div2" id="div2" ref="div2">
<div style="margin-top: 50px" name="div3" id="div3" ref="div3">
<div style="margin-top: 50px" name="div4" id="div4" ref="div4">
<div style="margin-top: 50px" name="div5" id="div5" ref="div5">
页面布局如下:

技术点:采用栅栏布局一行4栏4个div小卡片。定位方式采用相对定位margin,padding。
代码如下:










首页中子页面采用分页查询方式展示信息,采用get请求发送每页信息展示条目数和当前页面给后端,后端返回数据,前端渲染数据到页面。代码如下:
//请求分页查询数据
this.request.get(“/intr/policy/page”, {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
}
}).then(res => {
//后端返回给前端json对象,前端发送给后端对象,后端映射,可以json,parm,url
this.Data = res.records
this.total = res.total
});
},
1.3.ESG数据展示页
载入页面时,向后端发送请求,若用户未登陆,报“401”异常,发出警告,跳转登录页面。若用户已登陆,后端返回数据,通过Echarts绘制图形。前端渲染,展示数据信息。代码如下:
this.request.get(“/echarts/members”).then(res=>{
//当权限验证不通过时给出提示
if(res.code == ‘401’){
alert(“请登录后查看数据!”)
// router.push(“/login”)
}
// option.xAxis.data = res.data.x
option.series[0].data = res.data
//数据准备完毕后再set
myChart.setOption(option)
})
页面布局如下:

1.4.ESG评估流程页面
展示MBS ESG评估流程,最后提供MBS评估报考下载,点击下载可以查看MBS评估的宁德时代ESG报告。布局如下:

技术点:通过标签,实现选择文件并携带文件参数访问http://‘+serverIp+’:9091/file/upload’接口地址实现文件上传功能。代码如下:

上传文件

通过在子路由file页面中,打开文件的url地址实现文件下载。访问下载接口的url地址为:/file/url。代码如下
download(url) {
//file后接url
window.open(url)
},
1.5.注册登录页面
前端通过 标签获取登录注册页面的用户信息,发送用户信息到/user/login和/user/register接口。后端处理数据,返回信息。
login() {
//校验表单
this.KaTeX parse error: Expected '}', got 'EOF' at end of input: … this.router.push(“/home”)
} else {
//失败返回失败信息
this.$message.error(res.msg)
}
})
}
});
}

register() {
  //校验表单
  this.$refs['userForm'].validate((valid) => {
    if (valid) {  // 表单校验合法
      if(this.user.password != this.user.confirmpassword){
        this.$message.error("两次输入密码不一致")
        return false;
      }

      this.request.post("/user/register", this.user).then(res => {
        //判断返回状态码
        if (res.code === '200') {
          this.$message.success("注册成功")
        } else {
          //失败返回失败信息
          this.$message.error(res.msg)
        }
      })
    }
  });
}

1.6.个人信息修改页面
采用标签选择本地图片,发送用户信息和头像给后端,后端更改数据库信息。最后返回数据,前端渲染展示数据,更改信息修改页面的用户信息,并更新父级组件的用户信息。代码和页面布局如下:
save() {
this.request.post(“/user”, this.form).then(res => {
if (res.code === ‘200’) {
this. m e s s a g e . s u c c e s s ( " 保存成功 " ) / / 触发父级更新 U s e r 的方法 t h i s . message.success("保存成功") // 触发父级更新User的方法 this. message.success("保存成功")//触发父级更新User的方法this.emit(“refreshUser”)
// 更新浏览器存储的用户信息
this.getUser().then(res => {
res.token = JSON.parse(localStorage.getItem(“user”)).token
localStorage.setItem(“user”, JSON.stringify(res))
})
} else {
this.$message.error(“保存失败”)
}
})
},

1.7.搜索功能

技术点:前端输入不完整信息,发送数据给后端,后端进行模糊查询,返回数据给前端。代码如下:

2.Android
1.
2.后端【按分层说明数据变量、函数逻辑、接口设计以及异常处理】

1)数据库设计:

2)接口设计
以接口 /home/foreign_news 为例,HomeForeignNewsMapper 继承于BaseMapper,可以实现自动拼接sql语句,所有的mapper都不需要编写一些通用方法也就是不用编写sql语句。可以提高开发效率。
国外新闻请求接口(分页查询):

@RestController
@RequestMapping(“/home”)

@Autowired
private HomeForeignNewsMapper homeForeignNewsMapper;

public class HomeController {

@PostMapping(“/foreign_news”)
public Result listForeignNews(@RequestBody PageParams pageParams){
Page page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();

Page<HomeForeignNews> newsPage = homeForeignNewsMapper.selectPage(page, queryWrapper);
List<HomeForeignNews> records = newsPage.getRecords();

Integer total = homeForeignNewsMapper.selectCount(queryWrapper);
return Result.success(new Records(total, records));

}
当接收到前端包含请求体为 {“page”:,“pageSize”:5} 的接口请求后,使用Mabatis-plus提供的接口创建一个Page,并进行分页查询;因为最后还要向前端返回一个总条数total,所以再进行一次count查询,最后对结果进行封装并返回前端。

3)JWT进行跨域登录

Controller层代码:
@RestController
@RequestMapping(“login”)
public class LoginController {

@Autowired
private LoginService loginService;

@PostMapping
public Result login(@RequestBody LoginParam loginParam){
    return loginService.login(loginParam);
}

}

Service 层代码和逻辑:
1.检查参数是否合法
2.根据用户名和密码去 user 表中查询 是否存在
3.如果不存在,登录失败
4.如果存在,使用jwt,生成token 返回给前端
5.token放入redis中,redis token:user信息 设置过期时间
(登录认证时候 先认证token字符串是否合法,去redis认证是否存在)
@Override
public Result login(LoginParam loginParam) {

    String account = loginParam.getAccount();
    String password = loginParam.getPassword();
    if (StringUtils.isBlank(account) || StringUtils.isBlank(password)){
        return Result.fail(ErrorCode.PARAMS_ERROR.getCode(), ErrorCode.PARAMS_ERROR.getMsg());
    }
    password = DigestUtils.md5Hex(password + slat);
    SysUser sysUser = sysUserService.findUser(account, password);
    if (sysUser == null){
        return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(), ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
    }
    String token = JWTUtils.createToken(sysUser.getId());

    redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
    return Result.success(token);

}
最后返回token给前端

4)邮箱注册
具体思路:
1.判断参数是否合法
2.判断账户是否存在,存在则返回:账号已经被注册
3.不存在,则注册用户
4.生成token
5.存入redis 并返回
6.注意加上事务,一旦中间的任何过程出现问题,注册的用户需要回滚

@Override
public Result register(LoginParam loginParam) {
    String account = loginParam.getAccount();
    String password = loginParam.getPassword();
    String nickname = loginParam.getNickname();
    String captcha = loginParam.getCaptcha();
    String email = loginParam.getEmail();
    if (StringUtils.isBlank(account)
        || StringUtils.isBlank(password)
        || StringUtils.isBlank(nickname)
        || StringUtils.isBlank(captcha)
        || StringUtils.isBlank(email)
    ){
        return Result.fail(ErrorCode.PARAMS_ERROR.getCode(), ErrorCode.PARAMS_ERROR.getMsg());//参数错误
    }
    SysUser sysUser = sysUserService.findUserByAccount(account);
    if (sysUser != null){
        return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(), ErrorCode.ACCOUNT_EXIST.getMsg());//账号已存在
    }
    String captchaJson = redisTemplate.opsForValue().get("CAPTCHA_"+email);
    if (StringUtils.isBlank(captcha)){ //过期
        return Result.fail(ErrorCode.EMAIL_ERROR.getCode(), "验证码过期");
    }else if (!captchaJson.equals(captcha)){
        return Result.fail(ErrorCode.EMAIL_ERROR.getCode(), ErrorCode.EMAIL_ERROR.getMsg());
    }

    sysUser = new SysUser();
    sysUser.setNickname(nickname);
    sysUser.setAccount(account);
    sysUser.setPassword(DigestUtils.md5Hex(password + slat));
    sysUser.setCreateDate(System.currentTimeMillis());
    sysUser.setLastLogin(System.currentTimeMillis());
    sysUser.setEmail(email);
    this.sysUserService.save(sysUser);
    String token = JWTUtils.createToken(sysUser.getId());

    redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.MINUTES);
    return Result.success(token);
} 

5)登录拦截(未登录用户不能访问资源)

1.需要判断 请求的接口路径是否为 HandlerMethod(controller方法)
2.判断 token 是否为空, 如果为空:未登录
3.如果 token 不为空,登录验证 loginService checkToken
4.如果 认证成功 放行
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

@Autowired
private LoginService loginService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    if (!(handler instanceof HandlerMethod)){//还可能是 ResourceHttpRequestHandler,用来处理静态资源请求的handler,默认去classpath下的static目录去查询
        return true;
    }
    String token = request.getHeader("Authorization");

    if (StringUtils.isBlank(token)){
        Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg());
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().print(JSON.toJSONString(result));
        return false;
    }
    SysUser sysUser = loginService.checkToken(token);
    if (sysUser == null){
        Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg());
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().print(JSON.toJSONString(result));
        return false;
    }
    //验证成功,放行
    //我希望在controller中 直接获取用户的信息 怎么获取?
    UserThreadLocal.put(sysUser);
    return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    //如果不删除 ThreadLocal中用完的信息,会有内存泄露的风险
    //四种:强引用(不会被回收)、软引用(发生gc且内存不足,才会被回收)、弱引用(gc时直接回收)、虚引用(差不多不存在的引用)
    UserThreadLocal.remove();
}

}
登陆前:

登录后(每次请求携带token)

6)文件上传下载
1.逻辑思路:上传文件时,若文件内容不一样 得到不同标识码,先根据文件唯一标识码获取唯一的md5,根据唯一md5在数据库中查询是否有相同的文件,如果没有, 则上传文件到磁盘,记录url地址,如果有,直接返回url地址。最后存储url地址到数据库中。下载文件时,通过url获取硬盘中文件,再通过 ServletOutputStream输出流下载到本地浏览器。
2.代码实现
上传:
public String upload(@RequestParam MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
String type = FileUtil.extName(originalFilename);
long size = file.getSize();

    // 定义一个文件唯一的标识码
    String fileUUID = IdUtil.fastSimpleUUID() + StrUtil.DOT + type;

    File uploadFile = new File(fileUploadPath + fileUUID);
    // 判断配置的文件目录是否存在,若不存在则创建一个新的文件目录
    File parentFile = uploadFile.getParentFile();
    if(!parentFile.exists()) {
        parentFile.mkdirs();
    }


    String url;
    // 获取文件的md5,磁盘只有一份
    String md5 = SecureUtil.md5(file.getInputStream());
    // 从数据库查询是否存在相同的记录
    Files dbFiles = getFileByMd5(md5);
    if (dbFiles != null) {
        url = dbFiles.getUrl();
    } else {
        // 上传文件到磁盘
        file.transferTo(uploadFile);
        // 数据库若不存在重复文件,则不删除刚才上传的文件
        //本地下载文件的接口地址
        url = "http://localhost:9091/file/" + fileUUID;
        //阿里云服务器下载文件的接口地址

// url = “http://47.113.230.20:9091/file/” + fileUUID;
}

    // 存储数据库
    Files saveFile = new Files();
    saveFile.setName(originalFilename);
    saveFile.setType(type);
    saveFile.setSize(size/1024); // 单位 kb
    saveFile.setUrl(url);
    saveFile.setMd5(md5);
    fileMapper.insert(saveFile);
    return url;
}

下载
public void download(@PathVariable String fileUUID, HttpServletResponse response) throws IOException {
// 根据文件的唯一标识码获取文件
File uploadFile = new File(fileUploadPath + fileUUID);
System.out.println(uploadFile);
// 设置输出流的格式
ServletOutputStream os = response.getOutputStream();
response.addHeader(“Content-Disposition”, “attachment;filename=” + URLEncoder.encode(fileUUID, “UTF-8”));
response.setContentType(“application/octet-stream”);
// 读取文件的字节流
os.write(FileUtil.readBytes(uploadFile));
os.flush();
os.close();
}

3.算法

  1. 模型构建思路
  2. 项目提出的问题背景:
    在不同的行业内同样的指标数据对于行业而言重要程度是不同的,不能一概而论;而在同一个行业内,不同的指标对于行业的影响力也有所不同,有些是关键问题,有些又不是那么重要。但是如何能够实现提供不同的指标数据之后,得到准确合理的esg评分呢?换句话说我们需要得到每个指标的权重,来计算得到esg分数。
    我们想到了可以以行业为单位,设计出我们(数据分析师)认为足够重要的指标,获取这些数据之后,构建模型获取每个指标的权重。模型暂且构建为y = wx。【x指标数据,维度为指标个数,w表示每个指标的权重,y为评估分数】
  3. 项目目标:
    首先规定一系列指标x_header和反应企业经营效果的评估标准y;每个指标和标准分别给定数据,假定指标数据为X(x1,x2,……xn)【一共n个属性】,标准为y。构建关系y=Wx。企图求出权重W。最终可以实现给定一组指标数据x_test,可以直接得出最终的评分。
  4. 模型选取:
    想要训练得到得到恰当的权重值,可以使用多元线性回归或者感知机来计算,本项目选取MLP感知机来实现。因为感知机可以有多个隐藏层,当隐藏层有多个节点,隐藏层有多层的时候,其权重不具备特备深刻的含义【不能直观感受某些标签更加重要,而某些标签不重要】。因此我们设计隐藏层只有一层,对比隐藏层只有一个节点(权重只有1个)和20个节点(每个属性有20个权重)的情况。
  5. 计算结果处理过程:
    由于源数据量较小,分析工程量大,只完成19、20年数据分析。针对面对数据量小的难题分别做出以下尝试并进行结果分析:
  6. 利用源数据进行分析,标准化处理之后输入MLP模型,MLP模型:一层隐藏层:分别设置20个节点和1个节点。当设置20个节点由于一个属性会拥有20个权重,最终权重为20个权重的和。
  7. 设置5次不同的随机种子得出5轮权重,求出权重值的平均值。
  8. 对平均权值做一个偏移——使权重都变成正数。偏移1
  9. 求出权值对应权值总和所占比例。——》最终该标签对应的权重。
  10. 围绕源数据将数据进行随机化操作(波动范围在-1.5~1.5倍源数据),随机波动不具有一致性,分别构造出202个和20002(包含源数据),标准化处理之后,输入MLP模型,MLP模型:一层隐藏层:分别设置20个节点和1个节点。当设置20个节点由于一个属性会拥有20个权重,最终权重为20个权重的和。
  11. 设置5次不同的随机种子得出5轮权重,求出权重值的平均值。
  12. 真实的2条源数据作为测试集:利用平均权重*真实的2条源数据对比标签label得分,判断得出最相近的模型设置。
  13. 求出5个随机种子结果的平均权重、权重偏移、权重比例,将权重比例作为最后每个标签的权重。
  14. 每个指标的权重*源数据打分得出最终实际的评分。
  15. 编码准备
  16. 指标设计:选取新能源行业进行该行业指标设计,并且标注指标得分如何计算【根据什么数字,如何计算出结果】。
  17. 确定标签label:百分制:净利润(1/3)+基本每股收益(1/3)+总市值(1/3)。
  18. 获取数据:分析企业年报和社会责任报告获取数据,查询相关数据库。得到对应指标的评分。
  19. 数据标准化处理:为了模型模拟准确,应该将所有数据(指标评分)缩放到同一范围内:0-100。有些由于无法获得行业水平直接利用数据比例为得分的情况,最终使用最大最小放缩比例来来使所有数据被放缩到0-1之间。
  20. 编码
  21. 数据构造

for i in range(10000):
temp = numpy.random.rand(1, len(x_train[0])) + 0.5
for j in (range(temp.shape[1])):
temp[0][j] = temp[0][j] * x_train[0][j]
x_train = numpy.row_stack((x_train,temp))

for i in range(10000):
temp = numpy.random.rand(1, len(x_train[1])) + 0.5
for j in range(temp.shape[1]):
temp[0][j] = temp[0][j] * x_train[1][j]
x_train = numpy.row_stack((x_train,temp))

for i in range(10000):
temp = numpy.random.rand(1,1) + 0.5
a = y_train[0] * temp[0][0]
y_train.append(a)

for i in range(10000):
temp = numpy.random.rand(1,1) + 0.5
a = y_train[1]* temp[0][0]
y_train.append(a)
2. # 提取数据
with open(‘test2.csv’, mode=‘r’, newline=“”, encoding=“utf-8”) as csv_file:
reader = csv.reader(csv_file)
reader = list(reader)
reader.remove(reader[0])
x_train = reader

数据预处理

for item in x_train:
for j in range(len(item)):
item[j] = float(item[j])

x_train = numpy.array(x_train)
y_train = numpy.array(y_train)
y_train = y_train.astype(‘int’)

数据标准化处理

min_max_scaler = preprocessing.MinMaxScaler()
x_train = min_max_scaler.fit_transform(x_train)

搭建模型

clf = MLPClassifier(activation=‘relu’, alpha=1e-05, batch_size=‘auto’, beta_1=0.9,
beta_2=0.999, early_stopping=False, epsilon=1e-08,
hidden_layer_sizes=(20), learning_rate=‘constant’,
learning_rate_init=0.001, max_iter=3000, momentum=0.9,
nesterovs_momentum=True, power_t=0.5, random_state=1, shuffle=True,
solver=‘lbfgs’, tol=0.0001, validation_fraction=0.1, verbose=False,
warm_start=False)

训练数据

clf.fit(x_train, y_train)

提取权重

weight = clf.coefs_
result = []
array = weight[0]
r = []
for i in range(len(array)):
sum = 0
for item in array[i]:
sum += item
r.append(sum)

for i in range(len(array[0])):
temp = []
for j in range(len(array)):
sum += array[j][i]
temp.append(array[j][i])
result.append(temp)
result.append®

数据输出

with open(“result.csv”,mode=‘w’,newline=“”, encoding=“utf-8”) as csv_file:
writer = csv.writer(csv_file)
writer.writerows(result)

  1. 结果评估
  2. 首先对比2个源数据,202,20002个构造数据的权重测试得分,对比发现源数据2的趋势最匹配结果。分析:虽然构造数据量足够大,但是由于数据随机构造,其排布规律并不符合实际企业得分规律,也就是基本都是噪声数据,没有有效规律可学习,因此模型得到的数据并不合理。
  3. 接着分别对比20个结点和1个结点的情况,对比发现,20个结点趋势【得分值相近对比;二者得分数据差别趋势对比;5次随机种子权重波动是否明显差异】明显好于1个结点。
  4. 最终选择只有两个源数据,隐藏层有20个结点的权重结果进行更进一步的分析。
  5. 可行性分析
  6. 逻辑合理,有一定可行性。
  7. 数据量过小可能造成模型欠拟合,无法得出更加准确的模型。
  8. 评分合理性有待考量,部分指标为非强制计算型数据,多根据自身客观判断打分,可能存在片面性、不合理性。
  9. 难点分析
  10. 指标设计是否具有针对性。
  11. 持有行业平均水平的数据,才能给一家公司打相对合理的分数,但是整个行业的相关数据难以获取。
  12. 训练人工神经网络模型必须要大量的数据才能得出合理的模型——恰当的权重。但是关键问题在于有效数据量很小【有确定年份的准确披露数据几乎为0】。只能保证模型理论自洽,但是实操的准确性有待考量。
  13. 而且由于模型训练具有特征提取的目的,必须保证源数据规律一致性贴近实际、反应真实企业发展情况,才能学习其背后的变化规律,提取出准确完整的标签权重。
  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在后端分离的项目中,HTML主要负责前端界的展示和结构。在这种架中,前端通过AJAX或Fetch等技与后端API进行通信,获取数据并态更新页面内容。 以下是一个简单的示例,展示了一个基本的前界面结构: ```html <!DOCTYPE html> <html> <head> <title>前后端分离项目</title> <link rel="stylesheet" type="text/css" href="styles.css"> <script src="script.js"></script> </head> <body> <header> <h1>前后端分离项目</h1> </header> <nav> <ul> <li><a href="#">首页</a></li> <li><a href="#">关于</a></li> <li><a href="#">联系我们</a></li> </ul> </nav> <main> <h2>欢迎访问我们的项目</h2> <p>这是一个前后端分离的项目示例。</p> <div id="data-container"> <!-- 这里将通过JavaScript动态加载数据 --> </div> </main> <footer> <p>版权所有 © 2022 我的项目</p> </footer> </body> </html> ``` 在这个示例中,`<head>` 标签中引入了一个外部的样式表文件 `styles.css` 和一个外部的脚本文件 `script.js`。样式表文件用于定义页面的外观,脚本文件用于处理与后端API的通信和数据更新。 页面结构包括一个 `<header>` 元素用于显示项目标题,一个 `<nav>` 元素用于导航链接,一个 `<main>` 元素用于主要内容展示,以及一个 `<footer>` 元素用于显示页脚信息。 在 `<main>` 元素中,我们可以使用 `<div>` 元素的 `id` 属性为 `data-container`,用于动态加载后端API返回的数据。 需要注意的是,这只是一个简单的示例,实际的前端开发中可能涉及更多的HTML标签和交互逻辑。同时,前端也需要通过CSS和JavaScript等技术来美化页面、处理用户交互和实现其他功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值