数据管理平台

这篇具有很好参考价值的文章主要介绍了数据管理平台。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

数据管理平台项目

业务1-登录验证


功能: 完成验证码登录,后端设置验证码默认为:246810

代码步骤:
  1. 在utils/request.js配置 axios 请求基地址,提取公共前缀地址,配置后axios请求时都会baseURL + url。
  2. 收集手机号码验证数据
  3. 基于axios调用验证码登录接口
  4. 使用Bootstrap的Alert警告框反馈结果给用户.

##### 业务代码
  1. 在utils/request.js配置 axios 请求基地址,提取公共前缀地址,配置后axios请求时都会baseURL + url。
// axios 公共配置
// 基地址
axios.defaults.baseURL = 'http://geek.itheima.net'
  1. 收集手机号码验证数据
document.querySelector('.btn').addEventListener('click', () => {
    const form = document.querySelector('.login-form')
    const data = serialize(form ,{hash: true, empty: true})
    console.log(data)

  1. 基于axios调用验证码登录接口
    // 1.3 基于 axios 调用验证码登录接口
    axios({url: '/v1_0/authorizations',method: 'POST',data}).then(result => {
        console.log(result)
        //  1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户

        // 自己封装好了一个弹框插件,直接调用即可
        myAlert(true,'登录成功')

    }).catch(error => {
        console.dir(error.response.data.message)
        myAlert(false,error.response.data.message)
    })
})
  1. 自己封装好的弹框插件
// 弹窗插件
// 需要先准备 alert 样式相关的 DOM
/**
 * BS 的 Alert 警告框函数,2秒后自动消失
 * @param {*} isSuccess 成功 true,失败 false
 * @param {*} msg 提示消息
 */
function myAlert(isSuccess, msg) {
  const myAlert = document.querySelector('.alert')
  myAlert.classList.add(isSuccess ? 'alert-success' : 'alert-danger')
  myAlert.innerHTML = msg
  myAlert.classList.add('show')

  setTimeout(() => {
    myAlert.classList.remove(isSuccess ? 'alert-success' : 'alert-danger')
    myAlert.innerHTML = ''
    myAlert.classList.remove('show')
  }, 2000)
}

验证码登录流程
  1. 输入手机号,点击发送验证码。
  2. 携带手机号,调用服务器发送短信验证码接口
  3. 为此手机号生成验证码,记录生成的时间,并存在服务器
  4. 服务器携带手机号,验证码调用运营商接口
  5. 运营商通过基站给指定的手机号,发送验证码短信
  6. 运营商返回结果响应给服务端发送成功
  7. 服务端在将响应的结果返回给前端
  8. 根据手机短信填入验证码
  9. 在此携带手机号,验证码,调用验证码登录接口
  10. 服务器接收手机号和验证码,与之前用户输入的的记录对比,并且判断是否在有效期内。从而得出是否能够登录成功。

token 技术


token(访问权限的令牌),本质上是一串字符串

创建: 在正确的登录后,由后端签发并返回。

作用:判断是否有登录状态,控制访问权限。


token的使用

在登录状态时,是否能够访问内容页面

目标1:访问权限控制

代码步骤
  1. 判断无 token 令牌字符串,则强制跳转到登录页·
const token = localStorage.getItem('token')
if(!token) {
    location.href = '../login/index.html'
}

  1. 登录成功后,保存 token 令牌字符串到本地,并跳转到内容列表页面
localStorage.setItem('token',result.data.data.token)

然后通过令牌跳转页面

// 延迟时间跳转,停留提示后
setTimeout(() => {
// 登录成功能够通过令牌权限后,进行跳转到内容页
    location.href = '../content/index.html'
 },1500)

axios 请求拦截器


axios请求拦截器:在发起请求之前,触发的配置函数,对请求参数进行额外的配置。

语法

问题:很多接口都需要携带 token 令牌字符串。

解决:在请求拦截器统一设置公共样式headers选项。

axios({
    //因为我们给axios配置了基地址
    url: '目标资源路径',
    headers: {
      Authorization:`Bearer${localStorage.getItem('token')}`
    }
})

代码示例
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    // 统一携带  token  令牌字符串在请求头上
    const token = localStorage.getItem('token')
    // 本地有token的话,则在配置对象中配置token令牌权限
    token && (config.headers.Authorization = `Bearer ${token}`)
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  }); 

axios响应拦截器


axios 响应拦截器:响应回到then / catch之前, 触发的拦截函数,对响应结果统一处理


例如身份验证失败,统一做出判断并处理(身份验证失败,我们就直接就将关闭权限)


// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
}, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么,例如401 身份验证失败情况做出处理
    console.dir(error)
    if (error?.response?.status === 401) {
        alert('身份验证失败,请重新登录')
        localStorage.clear()
        location.href = '../login/index.html'
    }
    return Promise.reject(error);
});

对响应的结果做出处理


优化axios响应结果


axios直接接受服务器返回的响应结果

    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么.例如:直接返回服务器的响应结果对象
    const result = response.data
    return result;

这样result === response.data 我们再写的时候可以省略这部分。


发布文章-富文本编辑器


富文本:带样式,多格式的文本,在前端一般使用标签配合内联样式实现。

  1. 准备HTML标签结构
                <!-- 富文本编辑器位置 -->
                <div id="editor—wrapper">
                  <div id="toolbar-container"><!-- 工具栏 --></div>
                  <div id="editor-container"><!-- 编辑器 --></div>
                </div>
                <!-- 记录富文本内容-用于表单收集 -->
                <textarea name="content" class="publish-content" hidden></textarea>

  1. 引入对应的js和css的插件
  2. 在准备相应的样式
  3. 根据文档引入核心的js代码

文档:https://www.wangeditor.com/


发布文章-频道列表


步骤

  1. 获取频道列表数据
  2. 展示到下拉菜单中

代码示例

// 1.1 获取频道列表数据
async function setChannleList() {
   const res = await axios({
        url: '/v1_0/channels'
    })
    console.log(res)
    // 1.2 展示到下拉菜单中
    const htmlStr = `<option value="" selected="">请选择文章频道</option>` + res.data.channels.map(item => `<option value="${item.id}">${item.name}</option>`).join('')
    console.log(htmlStr)
    document.querySelector('.form-select').innerHTML = htmlStr
}
setChannleList()

事先考虑是否需要复用,需要则使用函数配和async和await来修饰。


发布文章-封面设置


步骤:

  1. 准备标签结构样式
  2. 选择文件并保存在FormData
  3. 单独好上传图片并得到图片url地址
  4. 回显并切换img标签展示(隐藏 + 号 上传标签)

注意:图片地址临时存储在img标签上,并未和文章关联保存。


// 2.1 准备标签结构和样式
// 2.2 选择文件并保存在 FormData
document.querySelector('.img-file').addEventListener('change',async e => {
    const file = e.target.files[0]
    const fd = new FormData()
    fd.append('image',file)
    // 2.3 单独上传图片并得到图片 URL 网址
    const res = await axios({
        url: '/v1_0/upload',
        method: 'POST',
        data: fd
    })
    console.log(res)
    // 2.4 回显并切换 img 标签展示(隐藏 + 号上传标签)
    const imgURL = res.data.url
    document.querySelector('.rounded').src = imgURL
    // 显示图片,添加show
    document.querySelector('.rounded').classList.add('show')
    // 隐藏基础的 + 号 上传标签 添加hide类
    document.querySelector('.place').classList.add('hide')
})

// 优化:点击 img 可以重新切换封面
// 思路:img 点击  => 用 js 方式触发文件选择元素  click  事件方法
document.querySelector('.rounded ').addEventListener('click', () => {
    // 调用click()方法可以模拟用户触发该类绑定的点击事件。通过调用click()方法,就相当于以编程方式触发了与该类相关联的点击事件。
    document.querySelector('.img-file').click()
})


发布文章-收集并保存


步骤:

  1. 基于 form-serialize 插件收集表单数据对象
  2. 基于 axios 提交到服务器保存
  3. 调用 Alert 警告框反馈结果给用户
  4. 置表单并跳转到列表页

// 3.1 基于 form-serialize 插件收集表单数据对象
document.querySelector('.send').addEventListener('click', async e => {
    const form = document.querySelector('.art-form')
    const data = serialize(form, { hash: true, empty: true })
    //发布文章的时候才不需要  id  属性,所以可以删除掉(id 为了后续做编辑使用)
    delete data.id
    console.log(data)
    // 自己收集封面图片地址,并保存到  data  对象中
    data.cover = {
        type: 1,
        images: [document.querySelector('.rounded').src]
    }
    // 3.2 基于 axios 提交到服务器保存
    try {
        const result = await axios({
            url: '/v1_0/mp/articles',
            method: 'POST',
            data
        })
        // 3.3 调用 Alert 警告框反馈结果给用户
        myAlert(true, '发布成功')

        // 3.4 重置表单并跳转到列表页

        form.reset()
        // 封面需要手动重置
        document.querySelector('.rounded').src = ''
        document.querySelector('.rounded').classList.remove('show')
        document.querySelector('.place').classList.remove('hide')
        // 富文本编辑器
        editor.setHtml('')
    
        setTimeout(() => {
            location.href = '../content/index.html'
        },1500)
    } catch(error) {
        myAlert(false, error.response.data.message)
    }
})


内容管理-文章列表展示


实现步骤:

* 1.1 准备查询参数对象

* 1.2 获取文章列表数据

* 1.3 展示到指定的标签结构中

注意:查询参数不能封装到函数里面,因为他不是永远固定的,然后再将获取和渲染封装到函数里面,后面很多操作都需要使用到获取和渲染,方便我们操作,下次直接调用函数即可。

代码示例
// 1.1 准备查询参数对象
const queryObj = {
    status: '',   //文章的状态    (1-待审核   2-通过审核   )  空字符串全部
    channel_id: '',  //文章频道, id  空字符串全部
    page: 1,   //当前页码
    per_page: 2     //当前页面的条数
}
// 1.2 获取文章列表数据
async function setArtileList() {
    const res = await axios({
        url: '/v1_0/mp/articles',
        params: queryObj
    })
    console.log(res)
    // 1.3 展示到指定的标签结构中
   const htmlStr = res.data.results.map(item => `<tr>
    <td>
      <img src="${item.cover.type === 0 ? `https://img2.baidu.com/it/u=2640406343,1419332367&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=708&amp;h=500"` : item.cover.images[0]} alt="">
    </td>
    <td>${item.title}</td>
    <td>
    ${item.status === 1 ? `<span class="badge text-bg-success">待审核</span>` : `<span class="badge text-bg-primary">审核通过</span>`}
    </td>
    <td>
      <span>${item.pubdate}</span>
    </td>
    <td>
      <span>${item.read_count}</span>
    </td>
    <td>
      <span>${item.comment_count}</span>
    </td>
    <td>
      <span>${item.like_count}</span>
    </td>
    <td>
      <i class="bi bi-pencil-square edit"></i>
      <i class="bi bi-trash3 del"></i>
    </td>
  </tr>`).join('')
  document.querySelector('.art-list').innerHTML = htmlStr
}
setArtileList()


内容管理-筛选功能


  1. * 2.1 设置频道列表数据

    * 2.2 监听筛选条件改变,保存查询信息到查询参数对象

    * 2.3 点击筛选时,传递查询参数对象到服务器

    * 2.4 获取匹配数据,覆盖到页面展示

代码示例
// 2.1 设置频道列表数据
async function setChannleList() {
    const res = await axios({
        url: '/v1_0/channels'
    })
    console.log(res)
    // 1.2 展示到下拉菜单中 
    const htmlStr = `<option value="" selected="">请选择文章频道</option>` + res.data.channels.map(item => `<option value="${item.id}">${item.name}</option>`).join('')
    console.log(htmlStr)
    document.querySelector('.form-select').innerHTML = htmlStr
}
setChannleList()

// 2.2 监听筛选条件改变,保存查询信息到查询参数对象
// 筛选状态标记数字 -> change 事件   -> 绑定到查询参数对象上
document.querySelectorAll('.form-check-input').forEach(radio => {
    radio.addEventListener('change', e => {
        queryObj.status = e.target.value
    })
})
// 筛选频道的 id  -> change 事件 -> 绑定到查询参数对象上
document.querySelector('.form-select').addEventListener('change', e => {
    queryObj.channel_id = e.target.value
})


// 2.3 点击筛选时,传递查询参数对象到服务器
document.querySelector('.sel-btn').addEventListener('click', () => {
    // 2.4 获取匹配数据,覆盖到页面展示
    setArtileList()
})


内容管理-分页功能


* 3.1 保存并设置文章总条数

* 3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据

* 3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据

* 3.1 保存并设置文章总条数

let totalCount = 0   //保存文章总条数  
// 3.1 保存并设置文章总条数
   const totalStr = totalCount = res.data.total_count
    // 将获取到的返回数据个数插入到页面中
    document.querySelector('.total-count').innerHTML = `${totalStr}`

// 3.1 保存并设置文章总条数

//  3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
document.querySelector('.next').addEventListener('click', () => {
    // 当前页码小于最大页码数才能翻页
    if (queryObj.page < Math.ceil(totalCount / queryObj.per_page)) {
        // 当前页增加
        queryObj.page++
        document.querySelector('.page-now').innerHTML = `${queryObj.page}`
        // 调用函数,渲染最新数据
        setArtileList()
    }
})
//  3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
document.querySelector('.page-link').addEventListener('click', () => {
    // 判断当前页码大于1的时候才能够执行
    if (queryObj.page > 1) {
        queryObj.page--
        document.querySelector('.page-now').innerHTML = `${queryObj.page}`
        // 调用函数,渲染最新数据
        setArtileList()
    }
})

内容管理-删除功能


  • 4.1 关联文章 id 到删除图标
  • 4.2 点击删除时,获取文章 id
  • 4.3 调用删除接口,传递文章 id 到服务器
  • 4.4 重新获取文章列表,并覆盖展示
  • 4.5 删除最后一页的最后一条,需要自动向前翻页
// 4.1 关联文章 id 到删除图标
    <td data-id="${item.id}">
      <i class="bi bi-pencil-square edit"></i>
      <i class="bi bi-trash3 del"></i>
    </td>
//  4.2 点击删除时,获取文章 id
document.querySelector('.art-list').addEventListener('click',async e => {
    //判断点击的是否为删除元素
    if (e.target.classList.contains('del')) {
        const delId = e.target.parentNode.dataset.id
        console.log(delId)

        // 4.3 调用删除接口,传递文章 id 到服务器
        const res = await axios({
            url: `/v1_0/mp/articles/${delId}`,
            method: 'DELETE',
        })
        // 4.5 删除最后一页的最后一条,需要自动向前翻页
        const children = document.querySelector('.art-list').children
        if (children.length === 1 && queryObj.page !== 1) {
            queryObj.page--
            document.querySelector('.page-now').innerHTML = `${queryObj.page}`
        }

        // 4.4 重新获取文章列表,并覆盖展示
        setArtileList()
    }

})


内容管理-编辑文章


\* 4.1 页面跳转传参(URL 查询参数方式)

* 4.2 发布文章页面接收参数判断(共用同一套表单)

* 4.3 修改标题和按钮文字

* 4.4 获取文章详情数据并回显表单


//跨页面传参  传递id值

// 点击编辑时,获取文章 id,跳转到发布文章页面传递文章 id 过去(使用事件委托)
document.querySelector('.art-list').addEventListener('click', e => {
    // 判断用户点击的是哪一个标签对象
    if (e.target.classList.contains('edit')) {
        // 拿到事件对象父级中的自定义属性 id值
        const artId = e.target.parentNode.dataset.id
        location.href = `../publish/index.html?id=${artId}`
    }
})

// 4.1 页面跳转传参(URL 查询参数方式)     立即执行函数,自己调用自己,防止变量污染
;(function () {
    // console.log(location.search)//查看查询参数字符串,就是?号后面的

    // 4.2 发布文章页面接收参数判断(共用同一套表单)
    const paramsStr = location.search
    const params = new URLSearchParams(paramsStr)       //分隔网址后面的查询参数
    params.forEach(async (value,key) => {
        // console.log(value,key)

        // 4.3 修改标题和按钮文字
        if (key === 'id') {
            // 当前有要编辑的文章 id 被传入过来
            document.querySelector('.title span').innerHTML = '修改文章'
            document.querySelector('.send').innerHTML = '修改'
            // 4.4 获取文章详情数据并回显表单
           const res = await axios({
                url: `/v1_0/mp/articles/${value}`,
            })
            console.log(res)
            // ***组织我需要的数据对象,我后续遍历回显到页面上做铺垫
            const dataObj = {
                channel_id: res.data.channel_id,
                title: res.data.title,
                rounded: res.data.cover.images[0],//封面图
                content: res.data.content,
                id: res.data.id
            }
            // ***遍历数据对象属性,映射到页面元素上,快速赋值
            Object.keys(dataObj).forEach(key => {
                console.log(key)
                if (key === 'rounded') {
                    // 封面设置
                    if (dataObj[key]) {
                        document.querySelector('.rounded').src = dataObj[key]
                        document.querySelector('.rounded').classList.add('show')
                        document.querySelector('.place').classList.add('hide')
                    } 
                } else if (key === 'content') {
                    // 富文本内容
                    editor.setHtml(dataObj[key])
                } else {
                    // 用数据对象属性名,作为标签 name 属性选择器值来找到匹配的标签
                    document.querySelector(`[name=${key}]`).value = dataObj[key]
                }
            })

        }
    })

})();


内容管理-编辑-保存文章


* 5.1 判断按钮文字,区分业务(因为共用一套表单)

* 5.2 调用编辑文章接口,保存信息到服务器

* 5.3 基于 Alert 反馈结果消息给用户


document.querySelector('.send').addEventListener('click', async e => {
    // 5.1 判断按钮文字,区分业务(因为共用一套表单)
    if (e.target.innerHTML !== '修改') return
    // 修改文章逻辑

    // 5.2 调用编辑文章接口,保存信息到服务器
    try {
        const form = document.querySelector('.art-form')
        const data = serialize(form, { hash: true, empty: true })
        console.log(data)
        const res = await axios({
            url: `/v1_0/mp/articles/${data.id}`,
            method: 'PUT',
            data: {
                ...data,
                cover: {
                    type: document.querySelector('.rounded').src ? 1 : 0,
                    images: [document.querySelector('.rounded').src]
                }
            }
        })
        console.log(res)
        myAlert(true,'修改文章成功')
    } catch (error) {
        myAlert(false,'error.response.data.message')

    }
})


退出登录


\* 3.1 绑定点击事件

* 3.2 清空本地缓存,跳转到登录页面文章来源地址https://www.toymoban.com/news/detail-658926.html

// 3.1 绑定点击事件
document.querySelector('.quit').addEventListener('click', e => {
    // 3.2 清空本地缓存,跳转到登录页面
    localStorage.clear()
    location.href = '../login/index.html'
})

到了这里,关于数据管理平台的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 服务器管理平台开发(2)- 设计数据库表

    本篇文章主要对数据管理平台数据库表设计进行介绍,包括单库多表设计、SQL语句、视图构造等 设备品牌、序列号、型号等使用业务主表进行记录,逻辑磁盘、PCI设备可能出现1对N的情况,分别使用PCI设备表、Mac地址表、逻辑磁盘表、应用程序表、登录日志表进行记录 构建虚

    2024年01月22日
    浏览(58)
  • 开源快速开发平台:做好数据管理,实现流程化办公!

    做好数据管理,可以提升企业的办公协作效率,实现数字化转型。开源快速开发平台是深受企业喜爱的低代码开发平台,拥有多项典型功能,是可以打造自主可控快速开发平台,实现一对一框架定制的软件平台。在快节奏的社会中,开源快速开发平台依托优势特点深得客户喜

    2024年02月15日
    浏览(43)
  • Java智慧工地可视化APP信息管理平台源码(项目端、监管端、数据大屏端、APP端)

    智慧工地系统以推进施工过程管理信息化、数字化、智慧化为手段,促进第五代通信技术 (5G) 、大数据、智能设备、人工智能等与建筑工程管理进一步融合。智慧化工地建设全面加速,以数字技术助力建筑工地转型升级、提速增效、提档升级的成效全面显现,逐步实现工程项

    2024年02月02日
    浏览(65)
  • 共创无线物联网数字化新模式|协创数据×企企通采购与供应链管理平台项目成功上线

    近日,全球无线物联网领先者『协创数据技术股份有限公司』(以下简称“协创数据”)SRM采购与供应链项目全面上线,并于近日与企企通召开成功召开项目上线总结会。 基于双方资源和优势,共同打造了物联网特色的数字化采购供应链管理系统,通过数字技术实现对供应商

    2024年02月12日
    浏览(56)
  • 项目全生命周期管理、资产成果沉淀展示、算力资源灵活调度丨ModelWhale 云端协同创新平台全面赋能数据驱动科研工作

    新基建的浪潮如火如荼,国家顶层政策的引导不仅支持着由数据驱动各垂直领域中的新兴商业市场,也为相关科研市场的发展提供了众多机遇。 但持续的发展也带来了新的问题, 传统基础设施已逐渐不能响应新兴数据驱动研究所需的软硬件支持。 本文将从此类问题出发,为

    2024年02月09日
    浏览(42)
  • java碳排放数据信息管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

    一、源码特点     java Web碳排放数据信息管理系统是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环 境为TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0,使用java语言开发。 java Web碳排放数据信

    2024年01月22日
    浏览(50)
  • 数据治理管理平台——数据资产管理

    数据治理 中的资产管理是一切治理活动的起点,在数据治理活动中,占据首要地位,只有将数据真正地资产化,才能有序进行后续的深入挖掘与研究。 数据资产管理作为数据治理的重要组成部分,有效地将数据规范管理和数据处理进行能力整合,实现对具体数据的元数据描

    2024年02月15日
    浏览(40)
  • java排课管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

    一、源码特点     java排课管理系统是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0,使用java语言开发。 java web 排课管理系统1 下载地址

    2024年02月19日
    浏览(50)
  • java 学生信息管理系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

    一、源码特点     java 学生信息管理系统是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0,使用java语言开发。 java 学生信息管理系统 二、

    2024年02月09日
    浏览(52)
  • SSM培训报名管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

    一、源码特点   SSM 培训报名管理系统是一套完善的信息系统,结合SSM框架完成本系统,对理解JSP java编程开发语言有帮助系统采用SSM框架(MVC模式开发),系统具有完整的源代码和数据库,系统主 要采用B/S模式开发。 SSM培训报名管理系统1 前段主要技术html div js  后端主要技

    2024年02月08日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包