超好看前端三件套+后端node的个人网站【附代码与陈诉】 ...

种地  论坛元老 | 2025-2-16 05:16:28 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1070|帖子 1070|积分 3210

前端三件套+后端node,采用express和SQ lite3以及echarts,非常得当简单全栈开发练手。
全栈代码堆栈地点 
GitHub - zhiyog/personal-web: 前端三件套+后端node,采用express和SQ lite3,非常得当简单全栈开发练手
前端静态网页搭建 
zhiyog 
环境配置


后端node环境
  1. node官网
复制代码
express框架
  1. npm install express
复制代码
bcrypt加密
  1. npm install bcrypt
复制代码
SQ lite3
  1. npm install sqlite3
复制代码
multer下载
  1. npm install multer
复制代码
大概的漏洞:



  • localstorage的使用;
  • 图片修改multer上传
  • 其他拓展

下面是实验陈诉


一 实践任务描述


实验一:HTML+CSS实验

制作个人主页,要求:
1)符合HTML,CSS相关规范;
2)网页内容及布局不限(可参考下图);
3)不能使用任何框架
实验二:JavaScript 实验

在个人主页中增长以下内容:
1)在符合的位置显示故乡当前天气现象
2)直接调用高德Web服务API举行实现
实验三:后端步伐设计实验

在个人主页中增长以下内容:
1)支持网页访问计数
2)在符合的位置放置编辑链接
3)点击编辑链接进入密码验证界面(密码可预先存于数据库)
4)密码验证通过后进入个人主页内容修改页面
5)个人主页中可修改的字段根据个人主页内容自定义(如:电话、项目经历等),适当选择有代表性字段即可
6)有一个可编辑字段需为列表式的,如项目经历,提供相应的添加、删除、修改操作支持
7)支持更改照片
要求:不能使用框架(含JQuery)

二 项目文件布局及功能



三 页面效果展示


 
 
四 页面设计


五 核心技术点


5.1 CSS设计

5.1.1 响应式设计



  • 通过 @media 媒体查询调整了不同屏幕尺寸的样式。例如,在手机和小屏设备上调整了导航栏、布局宽度等元素,以确保页面顺应各种设备。
5.1.2 导航栏样式



  • .navigation 类定义了背景图像、固定背景、标题样式等,使用了 background-image 和 background-size 属性来确保背景图像在不同屏幕尺寸下的展示效果。
  • .navigation .buttom 通过 :hover 增长了按钮的放大效果。
5.1.3 卡片设计



  • .me-card 和 .carbox 提供了卡片和容器的基础样式,包括背景色、阴影、圆角等样式,增强了视觉效果。
  • 使用了 box-shadow、border-radius 和 transform 属性来优化视觉体验。
5.1.4 动态交互效果



  • @keyframes 动画在多个地方被使用,如 bmove 和 shine,分别用于按钮指示和卡片的动画效果,增长了页面的动感。
  • hover 效果被广泛应用于元素的样式变革,例如按钮和图片的交互。

5.2 ECharts图表

5.2.1 ECharts折线图


  • 数据设置:各种活动(学习、音乐、游戏、编码、家庭)的时间分配以及不同时间指标的对应值。
  • 种别和颜色:定义种别(活动)并为每个种别分配不同的颜色,以便更好地举行视觉区分。
  • 平滑线条:每个系列(活动)都用一条平滑的线条表现,而且线下方的地区用半透明颜色添补,以实现平滑的渐变效果。
  • 自定义轴标签: x 轴代表时间指标,标签仅显示 5 的倍数的值。
  • 工具提示:将鼠标悬停在图表点上时,工具提示会显示具体信息,显示特定指数下每个种别的值。
  • 响应式设计:图表设置为根据窗口大小动态调整大小,以在不同的屏幕尺寸上保持其布局。
5.2.2 ECharts雷达图



  • 视觉地图:此功能设置从绿色到黄色再到紫色的颜色渐变来表现数据值。视觉地图有助于将数值映射到颜色渐变上。
  • 雷达指标:雷达图使用五个指标,每个指标对应一个特定的欣赏器(IE,Safari,Firefox,Chrome)。
  • 渐变和强调:雷达图中的数据线呈现渐变效果。当鼠标悬停在数据线上时,线条的宽度和颜色会发生变革,地区会添补半透明颜色。
  • 系列生成:动态生成 28 个数据系列,每个系列代表五个指标的一组值,而且每个系列都有独特的颜色渐变。
  • 动态数据生成:使用模式(例如,淘汰或增长函数)生成数据值,从而使图表具有不断发展、变革的性质。
5.3 高德api

5.3.1 天气插件集成:



  • AMap.Weather插件:该脚本使用AMap Weather插件(AMap.plugin('AMap.Weather', function () {...})来获取及时天气数据。
  • 天气数据检索:该weather.getLive()方法获取指定城市(本例中为温县)的及时天气数据。
  • 天气信息显示:脚本提取并显示各种天气具体信息。
5.3.2 动态标志和信息窗口:



  • 创建标志:使用自定义图标(蓝色标志)在地图中心放置一个标志,并使用偏移量(new AMap.Pixel(-13, -30))调整位置。
  • 信息窗口:AMap.InfoWindow当用户与标志交互时,创建一个来显示天气信息。   

    • 信息窗口的内容是根据天气数据动态生成的。
    • 当标志初始化或悬停在标志位置上时,信息窗口会在地图上打开。

5.3.3 互动性:



  • 标志悬停事故:当用户将鼠标悬停在标志上时,天气信息会显示在信息窗口中。   

    • 该事故marker.on('mouseover', function () {...})用于在标志悬停时打开信息窗口。

5.3.4 美学和功能特点:



  • 自定义标志图标:使用自定义标志图标 ( https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png) 来表现地图上的位置。
  • 动态和信息内容:信息窗口包罗动态内容,以清楚的格式(使用 举行布局化布局)向用户提供具体的天气<h4>信息<p>。
    <!-- 地图 -->

    <div class="maps" id="maps">

      <div class="mode">Maps</div>

      <div class="map" id="map"></div>

      <script type="text/javascript">

        window._AMapSecurityConfig = {

          securityJsCode: "89b596490cca6364101b36c2d45e9f3e",

        }

      </script>

      <script src="https://webapi.amap.com/loader.js"></script>

      <script type="text/javascript"

        src="https://webapi.amap.com/maps?v=2.0&key=dbcb618758ba071072471d12ea02dcb8"></script>

      <script type="text/javascript">

        var map = new AMap.Map('map', { // 修改这里为 'map'

          resizeEnable: true,

          center: [113.0795, 34.9412],

          zoom: 12

        });

        AMap.plugin('AMap.Weather', function () {

          var weather = new AMap.Weather();

          // 查询及时天气信息

          weather.getLive('温县', function (err, data) {

            if (!err) {

              var str = [];

              str.push('<h4>及时天气</h4><hr>');

              str.push('<p>城市/区:' + data.city + '</p>');

              str.push('<p>天气:' + data.weather + '</p>');

              str.push('<p>温度:' + data.temperature + '℃</p>');

              str.push('<p>风向:' + data.windDirection + '</p>');

              str.push('<p>风力:' + data.windPower + ' 级</p>');

              str.push('<p>空气湿度:' + data.humidity + '</p>');

              str.push('<p>发布时间:' + data.reportTime + '</p>');

              var marker = new AMap.Marker({

                map: map,

                position: map.getCenter(),

                icon: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png', // 默认蓝色标志图标

                offset: new AMap.Pixel(-13, -30) // 偏移量调整,使图标中心对准位置

              });

              var infoWin = new AMap.InfoWindow({

                content: '<div class="info">' + str.join('') + '</div>',

                isCustom: true,


              });

              infoWin.open(map, marker.getPosition());

              marker.on('mouseover', function () {

                infoWin.open(map, marker.getPosition());

              });

            }

          });

        });

      </script>

    </div>

5.4 SQ lite3数据库

5.4.1 数据库设置和表创建:


  • sqlite3模块用于初始化 SQLite 数据库(database.db)。
  • 该users表包罗、和的列id,username以password确保该表可用于将来的用户身份验证或注册。
5.4.2 使用 bcrypt 举行密码哈希处理:


  • 该bcrypt库用于在将密码存储到数据库之前对其举行安全哈希处理。这对于确保用户密码安全存储而非以明文形式存储至关重要。
  • 该bcrypt.hash方法使用 10 轮盐值来为初始admin用户生成散列密码。
5.4.3 数据插入:


  • 散列密码被插入到users表中,确保敏感数据(如密码)安全存储。
  • 该INSERT INTO users查询确保假如用户已经存在(使用INSERT OR IGNORE),则不会插入重复的记录。
5.4.4 数据库连接清算:


  • 完成操作后(成功或错误后),数据库连接将关闭,以制止资源泄漏。
5.4.5 安全功能(散列密码):


  • 存储密码的最佳实践,即在使用 存储之前对密码举行哈希处理bcrypt,这使应用步伐更加安全,并防止以不安全的格式存储敏感信息。
const sqlite3 = require('sqlite3').verbose();

const db = new sqlite3.Database('database.db');

const bcrypt = require('bcrypt');

// 初始化数据库

// db.serialize(() => {

//   // 创建 users 表

 


//   db.run(`

//     CREATE TABLE IF NOT EXISTS users (

//       id INTEGER PRIMARY KEY AUTOINCREMENT,

//       username TEXT NOT NULL UNIQUE,

//       password TEXT NOT NULL

//     )

//   `);

//   // 插入默认用户

//   db.run(`

//     INSERT OR IGNORE INTO users (username, password)

//     VALUES ('admin', '123456') -- 修改为你需要的初始用户名和密码

//   `);

//   // 创建 visit_counts 表

//   db.run(`

//     CREATE TABLE IF NOT EXISTS visit_counts (

//       id INTEGER PRIMARY KEY AUTOINCREMENT,

//       count INTEGER NOT NULL DEFAULT 0

//     )

//   `);

//   // 初始化访问计数

//   db.run(`

//     INSERT OR IGNORE INTO visit_counts (count)

//     VALUES (0)

//   `);

// });

 // 加密密码

 

 // 初始化数据库

 db.serialize(() => {

   // 创建用户表

   db.run(`

     CREATE TABLE IF NOT EXISTS users (

       id INTEGER PRIMARY KEY AUTOINCREMENT,

       username TEXT UNIQUE NOT NULL,

       password TEXT NOT NULL

     )

   `);

 

   // 加密密码

   const username = 'admin';

   const plainPassword = 'admin123';

 

   bcrypt.hash(plainPassword, 10, (err, hashedPassword) => {

     if (err) {

       console.error('密码加密失败:', err);

       db.close(); // 在发生错误时也需要关闭数据库

       return;

     }

 

     // 插入初始化数据

     db.run(

       `INSERT INTO users (username, password) VALUES (?, ?)`,

       [username, hashedPassword],

       (err) => {

         if (err) {

           console.error('数据插入失败:', err);

         } else {

           console.log(`用户 ${username} 数据初始化完成`);

         }

         // 全部操作完成后关闭数据库

         db.close();

       }

     );

   });

 });

 


5.5 multer和localstorage实现编辑

5.5.1 Multer(文件上传和处理)

使用 Multer 上传文件


  • Multer 集成:代码使用Multer(固然您提供的代码中没有明确显示,但它通过调用fetch和服务器端处理暗示)来处理服务器上的文件上传。此库对于处理通常用于上传文件的 multipart/form-data 至关重要。
  • 图片处理:用户可以上传头像图片(头像),由服务器处理后保存到服务器的特定路径或云存储服务中。
服务器端交互


  • 图像上传:通过元素选择图像文件后<input type="file">,POST将向服务器端点(/upload)发出文件请求。Multer 将处理服务器端的文件解析和存储。
  • 服务器响应:然后服务器以包罗新文件路径的 JSON 对象举行响应,然后在客户端使用该对象来更新头像图像。
持久图像路径


  • 动态路径处理:文件上传成功后,服务器返回新的图片路径(filePath),该路径动态设置为头像的图片源(editHeadPicture.src)。
  • 错误处理:假如上传失败,代码会处理错误并提示用户,从而确保上传过程的稳定性。

5.5.2 LocalStorage(数据持久化与交互)

头像图片持久存储


  • LocalStorage 持久化:头像图片上传完成后,服务器返回图片路径后,会将新的图片路径存储在欣赏器的 中localStorage。这样可以保证纵然刷新或重新访问页面,头像图片也能持久化,无需重新上传。
  • 高效存储:头像图像路径作为字符串存储在localStorage键下"headPictureSrc",可轻松跨会话检索。
使用 LocalStorage 编辑文本


  • 可编辑文本元素:editableText和quoteText元素答应用户单击并编辑文本。新文本存储在 中localStorage,纵然页面重新加载后仍会保留。
  • 动态文本编辑localStorage:当用户更新文本时,文本会在用户确认更改后立即保存,确保更新的内容在会话中持久保存。
高效的列表处理


  • 在 LocalStorage 中存储列表:种别(如“项目”、“codeStacks”和“奖项”)以 JSON 数组形式存储在 中localStorage。这可确保列表数据不会在会话之间丢失。
  • 添加、编辑和删除项目:该应用答应用户添加、编辑和删除这些列表中的项目。每个更改都会反映出来,localStorage以便数据保持持久性。
加载和渲染


  • 页面加载时加载数据:加载页面时,localStorage将检索并相应地呈现存储在其中的数据(如头像图像路径和可编辑文本)。这使用户体验无缝衔接,并确保他们不会丢失之前的设置或修改。
  • 初始回退:假如未找到任何数据localStorage,则使用默认值(例如占位符图像),以确保纵然用户之前未与页面交互,页面也能按预期运行。

特征

Multer

Localstorage

数据类型

文件上传(例如图像、文档)

文本数据(字符串、JSON 数组等)

持久性

服务器端存储(图片路径、文件)

客户端持久性(数据保存在欣赏器中)

用例

上传和管理文件(图片上传等)

存储简单数据(文本、列表、偏好)

数据可用性

需要服务器来存储和提供文件

欣赏器中可获取将来全部访问的数据

安全

需要服务器端安全性(例如文件验证、权限)

仅限于客户端安全(无服务器端验证)

错误处理

处理与文件上传失败相关的错误(大小、格式等)

错误与 localStorage 容量和欣赏器支持有关

实行复杂性

需要后端设置(例如,带有 Express 的 Node.js、Multer)

无需后端,完全由前端处理




// 获取span元素

const editableText = document.getElementById('edit_hover_text');


// 从localStorage读取并设置初始文本(假如有保存的内容)

if (localStorage.getItem('textContent')) {

    editableText.textContent = localStorage.getItem('textContent');

}

// 添加点击事故,答应用户修改文本

editableText.addEventListener('click', () => {

    const currentText = editableText.textContent;

    const newText = prompt('编辑文本:', currentText);

    if (newText !== null && newText !== currentText) {

        editableText.textContent = newText;

        // 将修改后的文本保存到localStorage

        localStorage.setItem('textContent', newText);

    }

});

const quoteText = document.getElementById('edit_quote')

if (localStorage.getItem('textQuote')) {

    quoteText.textContent = localStorage.getItem('textQuote');

}

quoteText.addEventListener('click', () => {

    const currentText = quoteText.textContent;

    const newText = prompt('编辑文本:', currentText);

    if (newText !== null && newText !== currentText) {

        quoteText.textContent = newText;

        // 将修改后的文本保存到localStorage

        localStorage.setItem('textQuote', newText);

    }

});

// 加载列表数据并渲染

function loadList() {

    const categories = ['projects', 'codeStacks', 'awards'];

    categories.forEach(category => {

        const list = JSON.parse(localStorage.getItem(category)) || initialData[category];

        const listContainer = document.getElementById(`${category}-list`);

        listContainer.innerHTML = ''; // 清空现有列表

        list

            .filter(item => typeof item === 'string') // 筛选出字符串类型的数据

            .forEach(item => {

                const p = document.createElement('p');

                p.textContent = item; // 确保 item 是字符串

                p.onclick = () => editItem(category, p, item);

                const deleteBtn = document.createElement('span');

                deleteBtn.classList.add('delete-btn');

                deleteBtn.textContent = '×';

                deleteBtn.onclick = (e) => {

                    e.stopPropagation();

                    deleteItem(category, p, item);

                };

                p.appendChild(deleteBtn);

                listContainer.appendChild(p);

            });

    });

}

// 编辑条目

function editItem(category, p, oldText) {

    customPrompt('Edit item:', p.textContent.replace('×', '').trim(), (newText) => {

        if (newText !== null && newText !== oldText) {

            p.textContent = newText;

            // 添加删除按钮

            const deleteBtn = document.createElement('span');

            deleteBtn.classList.add('delete-btn');

            deleteBtn.textContent = '×';

            deleteBtn.onclick = (e) => { e.stopPropagation(); deleteItem(category, p, newText); };

            p.appendChild(deleteBtn);

            // 更新 localStorage 数据

            const list = JSON.parse(localStorage.getItem(category)) || initialData[category];

            const index = list.indexOf(oldText);

            if (index > -1) {

                list[index] = newText;

                localStorage.setItem(category, JSON.stringify(list));

            }

        }

    });

}

function deleteItem(category, element, item) {

    const list = JSON.parse(localStorage.getItem(category)) || initialData[category];

    const updatedList = list.filter(entry => entry !== item); // 移除匹配的项

    localStorage.setItem(category, JSON.stringify(updatedList)); // 更新 localStorage

    element.remove(); // 从 DOM 中移除对应的 <p>

}

// 增长新条目

function addItem(category) {

    customPrompt('Enter new item:', '', (newText) => {

        if (newText) {

            const list = JSON.parse(localStorage.getItem(category)) || initialData[category];

            list.push(newText);

            localStorage.setItem(category, JSON.stringify(list));

            loadList();

        }

    });

}

function customPrompt(title, defaultValue, callback) {

    // 创建遮罩层

    const overlay = document.createElement('div');

    overlay.style.position = 'fixed';

    overlay.style.top = '0';

    overlay.style.left = '0';

    overlay.style.width = '100vw';

    overlay.style.height = '100vh';

    overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';

    overlay.style.zIndex = '9998';

 

    // 创建弹窗容器

    const promptBox = document.createElement('div');

    promptBox.style.position = 'fixed';

    promptBox.style.top = '50%';

    promptBox.style.left = '50%';

    promptBox.style.transform = 'translate(-50%, -50%)';

    promptBox.style.width = '300px';

    promptBox.style.padding = '20px';

    promptBox.style.backgroundColor = '#fff';

    promptBox.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.2)';

    promptBox.style.borderRadius = '8px';

    promptBox.style.zIndex = '9999';

    promptBox.style.fontFamily = 'Arial, sans-serif';

 

    // 标题

    const titleEl = document.createElement('h3');

    titleEl.textContent = title;

    titleEl.style.margin = '0 0 10px';

    titleEl.style.fontSize = '18px';

    titleEl.style.color = '#333';

 

    // 输入框

    const input = document.createElement('input');

    input.type = 'text';

    input.value = defaultValue || '';

    input.style.width = '80%';

    input.style.padding = '10px';

    input.style.marginBottom = '10px';

    input.style.border = '1px solid #ddd';

    input.style.borderRadius = '4px';

    input.style.fontSize = '14px';

 

    // 按钮容器

    const buttonContainer = document.createElement('div');

    buttonContainer.style.textAlign = 'right';

 

    // 确认按钮

    const confirmButton = document.createElement('button');

    confirmButton.textContent = '确认';

    confirmButton.style.marginRight = '10px';

    confirmButton.style.padding = '8px 12px';

    confirmButton.style.border = 'none';

    confirmButton.style.borderRadius = '4px';

    confirmButton.style.backgroundColor = '#007bff';

    confirmButton.style.color = '#fff';

    confirmButton.style.cursor = 'pointer';

 

    // 取消按钮

    const cancelButton = document.createElement('button');

    cancelButton.textContent = '取消';

    cancelButton.style.padding = '8px 12px';

    cancelButton.style.border = 'none';

    cancelButton.style.borderRadius = '4px';

    cancelButton.style.backgroundColor = '#6c757d';

    cancelButton.style.color = '#fff';

    cancelButton.style.cursor = 'pointer';

 

    // 按钮点击事故

    confirmButton.addEventListener('click', () => {

      const result = input.value.trim();

      if (callback) callback(result);

      document.body.removeChild(promptBox);

      document.body.removeChild(overlay);

    });

 

    cancelButton.addEventListener('click', () => {

      if (callback) callback(null);

      document.body.removeChild(promptBox);

      document.body.removeChild(overlay);

    });

 

    // 组装元素

    buttonContainer.appendChild(confirmButton);

    buttonContainer.appendChild(cancelButton);

    promptBox.appendChild(titleEl);

    promptBox.appendChild(input);

    promptBox.appendChild(buttonContainer);

    document.body.appendChild(overlay);

    document.body.appendChild(promptBox);

 

    // 主动聚焦输入框

    input.focus();

  }

// 初始化加载页面内容

loadList();


5.6 Session 路由鉴权

5.6.1 Session 身份验证:

①express-session 中间件:


  • 用来存储用户会话数据,确保用户在登录后可以或许维持会话状态,而不需要每次请求都重新登录。
  • 配置了一个 secret 字段,确保会话数据被加密而且保持私密。
  • cookie 设置了会话的有效期为 1小时,即每个用户登录后的会话会连续1小时。
  • resave: false 和 saveUninitialized: false 确保不会在每次请求时逼迫重新保存会话。
② session.user:


  • 登录成功后,将用户信息(如 username)存储在 session 中,标识该用户已登录。
  • 这样,在用户访问需要认证的页面时,可以通过会话判断用户是否已登录。
5.6.2 鉴权中间件 requireAuth:

①requireAuth 中间件用于对特定的路由举行访问控制

  • 假如用户的 session.user 不存在(即未登录),则会重定向到登录页面 (/login.html)。
  • 假如用户已登录(session.user 存在),则答应访问后续的路由。
保护页面:

  • 例如,/edit.html 页面使用了 requireAuth,确保只有登录用户才气访问。
  • 这种方法确保了对于敏感操作或编辑页面,未登录的用户无法直接访问。
5.6.3 登录功能和密码加密:

登录 API (/api/login)

  • 使用 bcrypt 对密码举行加密处理和比对,确保用户密码的安全性。
  • 登录成功后,将用户信息存储在 session 中,以后请求都可以通过 session 判断用户是否已登录。
密码验证:

  • 登录请求通过查询数据库获取对应的用户名,并使用 bcrypt.compare 来验证输入的密码与存储在数据库中的哈希密码是否匹配。
  • 假如验证成功,则将 username 存储在 session.user 中,以便后续请求可以通过会话验证用户身份。
5.6.4 增长安全性:


  • Session 过期时间:通过设置会话 cookie 的有效期为 1小时,制止用户会话一直有效,增长了安全性。假如用户长时间没有操作,会话会主动过期。
  • 密码加密:使用 bcrypt 加密用户密码,而不是存储明文密码。纵然数据库泄漏,用户密码也不会被直接袒露。

// 配置 session 中间件

app.use(

  session({

    secret: 'your_secret_key', // 替换为随机的密钥字符串

    resave: false,

    saveUninitialized: false,

    cookie: { maxAge: 60 * 60 * 1000 }, // 会话连续时间(1小时)

  })

);

// 中间件:验证用户是否已登录

function requireAuth(req, res, next) {

  if (!req.session.user) {

    return res.redirect('/login.html'); // 假如没有登录,重定向到登录页面

  }

  next();

}



六 心得与领会


6.1 环境配置

node环境 node官网

express框架 npm install express

bcrypt加密 npm install bcrypt

SQ lite3 npm install sqlite3

multer下载 npm install multer

6.2 技术提升与收获



  • 技术能力提升:通过具体的项目实践,学会了如何使用各种技术(如Node.js、Express、Multer等)来办理现实题目。比如,使用中间件来处理用户会话,实现了用户鉴权功能,学习了如何保护路由以及如那边理文件上传等。
  • 工具和框架的应用:在项目中使用了bcrypt举行密码加密、sqlite3数据库操作等,这些技术提升了我对Web后端开发中安全性和数据持久化的理解。
6.3 前端与后端协作



  • 前后端交互:通过fetch举行前后端数据交换,实现了头像上传功能,而且使用localStorage举行前端数据的持久化,提升了用户体验。
  • 数据存储与同步:通过前后端的配合,实现了访问计数的持久化存储和更新,前端通过API获取访问数据,并展示出来,增强了页面的互动性。
6.4 不敷与改进



  • 性能优化:尽管项目功能实现完整,但在高并发的情况下,如何优化数据库操作、文件上传等环节,提升体系的性能,仍然是一个需要进一步探索的题目。
  • 用户体验:在用户体验方面,将来可以增长更多的交互性功能,比如修改用户资料、展示用户的具体信息等,使得体系更加完备。
  • localstorage的使用,图片修改multer上传

参考资料


  • 高德开放平台  高德开放平台 | 高德地图API
  • Jabin Peng个人主页  JabinPeng
  • WeiTingting个人主页  求职简历
  • 数字游牧人samuel主页 SamuelQZQ Blog | 数字游牧人
  • Echarts可视化 快速上手 - 使用手册 - Apache ECharts
  • https://juejin.cn/post/7242127432203173948?searchId=20241114104732F14886292F96EA0A881   

 



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

种地

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表