位置: IT常识 - 正文

分享项目 - Vue3 + TS + element-ui-plus 项目 -- Table表格表单(分享项目成果)

编辑:rootadmin
分享项目 - Vue3 + TS + element-ui-plus 项目 -- Table表格表单 文章目录前言项目地址以及怎么阅读别人的代码整体代码分页数据作者是怎么处理的 usePagination顺藤摸瓜找到 api 接口的封装api 接口再往底层找全局请求封装与请求拦截器 service.ts前言

推荐整理分享分享项目 - Vue3 + TS + element-ui-plus 项目 -- Table表格表单(分享项目成果),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:vue技术分享,分享项目的说说,分享项目话术,分享项目话术,分享项目经验,分享项目经验,分享项目要注意什么,分享项目要注意什么,内容如对您有帮助,希望把文章链接给更多的朋友!

今天看一个 ts 项目的 table 模块,亲身体验这是公司后台管理系统一定会使用到的,也是最常使用到的,这个项目对新手很友好,毕竟是一个相对来说比较空的项目模板,对于我来说就是一个学习的记录,一些技术的分享,手把手告知新手别人的代码怎么读,甚至还能帮该开源项目作者获取一些热度,我很乐于做这样的事情(已经争得原作者许可,感谢 🤓)项目地址:V3 Admin Vite

通过该文章可以学习到 :

element-ui-plus 的表单、表格等组件的使用怎么阅读他人的代码、怎么写出优雅炫酷的代码api 请求以及 api 请求拦截器、api 全局请求封装等知识点项目地址以及怎么阅读别人的代码分享项目 - Vue3 + TS + element-ui-plus 项目 -- Table表格表单(分享项目成果)

我们来看一下具体代码是怎么实现的,我读别人的代码喜欢先看一下大体目录结构、然后从页面功能入手,然后在 html 中找到该组件,然后查看该组件使用的方法等,一直相连关联到底层封装代码(或者直接看脚本逻辑,从脚本逻辑入手,看大家习惯)

功能、底层封装、页面结构等等知道了,自然而然就通了

我一步一步就标注了我对该代码的思考,希望对于大家有所帮助

整体代码<script lang="ts" setup>import { reactive, ref, watch } from "vue"import { createTableDataApi, deleteTableDataApi, updateTableDataApi, getTableDataApi } from "@/api/table"import { type FormInstance, type FormRules, ElMessage, ElMessageBox } from "element-plus"import { Search, Refresh, CirclePlus, Delete, Download, RefreshRight } from "@element-plus/icons-vue"import { usePagination } from "@/hooks/usePagination"// 加载状态,这也是 element-ui-plus 的一种加载方法,可以查看 html 元素并访问 element-ui-plus 官网来找到该变量有什么用处// 定义 loading 为响应式状态值,ts 限制为布尔类型const loading = ref<boolean>(false)// 自己封装的页面功能,可以转到 对应 src目录下 hooks 文件夹中的 usePagination 中查看对应方法的功能// 相应代码在本文章下面,可对应查看或者直接去 GitHub 下载宝藏博主的源码码进行查看const { paginationData, handleCurrentChange, handleSizeChange } = usePagination()//#region 增// 我们在 html 代码中可以看到 是使用在 el-dialog 组件的 v-module 属性上的,我们可以查看 element-ui-plus 文档查看该功能// v-model 控制这该组件是否显示const dialogVisible = ref<boolean>(false)// 表单对象实例 ts限制为表单实例 或 null// 在新增用户判断时需要使用到,被绑定在 表单的 ref 值上const formRef = ref<FormInstance | null>(null)// 表单输入值const formData = reactive({ username: "", password: ""})// 定义表单验证规则并使用 ts 进行类型规范const formRules: FormRules = reactive({ username: [{ required: true, trigger: "blur", message: "请输入用户名" }], password: [{ required: true, trigger: "blur", message: "请输入密码" }]})// 创建新用户/修改用户 方法const handleCreate = () => { // 判断表单实例是否存在 // 因为 validate 是 element-ui-plus 表单上的一个方法,所以需要使用到表单实例才可以使用该方法,现在我们知道了为什么要获取表单实例了 // validate 接收一个回调函数,或返回 Promise,执行之前是有一个前提的,需要表单实例是存在的 formRef.value?.validate((valid: boolean) => { if (valid) { // 如果 valid 存在,那么判断 currentUpdateId 是否为 undefined // currentUpdateId 是否有值决定着用户操作的是新增还是修改 if (currentUpdateId.value === undefined) { // 发起创建 table 请求,携带用户名与用户密码 createTableDataApi({ username: formData.username, password: formData.password }).then(() => { // 数据请求成功之后弹出提示信息 ElMessage.success("新增成功") // 并将弹框设置为不显示 dialogVisible.value = false // 这里的方法在下面 但是从命名就不难看出 这是新增成功之后重新请求一下所有的数据 保证页面数据的最新 getTableData() }) } else { // 前面也有提到 这里是一个炫酷写法,将新增和修改放在一个方法中,执行哪个方法取决于 currentUpdateId 是否有值 // 不得不佩服作者代码写得很棒 updateTableDataApi({ id: currentUpdateId.value, username: formData.username }).then(() => { ElMessage.success("修改成功") dialogVisible.value = false getTableData() }) } // 没有 valid 值,将会退出该方法不执行任何操作 } else { return false } })}// 读到这里就知道 currentUpdateId 是一个关于什么的值了// 我们查到找 html 代码发现当弹窗关闭的时候会触发该方法// 捋一下思路,也就是弹窗关闭,currentUpdateId 值会清空(用户信息也会清空)// 所以我们可以知道用户信息是为了下次打开弹框不会发生之前数据还显示出来的状况// 而 currentUpdateId 则是当前更新 ID ,该值为 undefined 需要执行的是新增,如果当前拥有用户id,那么执行的就是更新const resetForm = () => { currentUpdateId.value = undefined formData.username = "" formData.password = ""}//#endregion//#region 删// row 是当前点击列表项的数据const handleDelete = (row: any) => { ElMessageBox.confirm(`正在删除用户:${row.username},确认删除?`, "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }).then(() => { // deleteTableDataApi 是封装好的 request(ajax) 方法 // 我们可以看看作者是怎么封装数据请求的 deleteTableDataApi(row.id).then(() => { ElMessage.success("删除成功") getTableData() }) })}//#endregion//#region 改// 这里的修改方法只是给到了用户数据以及 当前ID 值,当用户点击确认按钮的时候才会发出真在的数据请求,将该值给到服务端处理// 所以这里只是一些简单的数据修改const currentUpdateId = ref<undefined | string>(undefined)const handleUpdate = (row: any) => { currentUpdateId.value = row.id formData.username = row.username formData.password = row.password dialogVisible.value = true}//#endregion//#region 查// 表格列表数据 -- 好的代码命名一看就知道是什么意思,是非常棒的const tableData = ref<any[]>([])// 输入框框实例const searchFormRef = ref<FormInstance | null>(null)// 输入框输入值const searchData = reactive({ username: "", phone: ""})// 获取所有列表数据进行页面的渲染const getTableData = () => { loading.value = true // 获取列表数据的 API getTableDataApi({ // 前俩个看命名也知道是分页相关值 currentPage: paginationData.currentPage, size: paginationData.pageSize, username: searchData.username || undefined, phone: searchData.phone || undefined }) .then((res) => { // 总数以及列表数据 paginationData.total = res.data.total tableData.value = res.data.list }) .catch(() => { // 如果数据请求发生错误,那么不显示数据列表 tableData.value = [] }) .finally(() => { // 无论请求成功或者失败 不显示加载图标 loading.value = false })}// 查询const handleSearch = () => { // 只有当数据处在第一页的时候才会刷新数据 if (paginationData.currentPage === 1) { getTableData() } // 跳转到第一页 paginationData.currentPage = 1}// 重置const resetSearch = () => { // resetFields 重置该表单项,将其值重置为初始值,并移除校验结果,这也是 element-ui-plus 组件实例上的方法 searchFormRef.value?.resetFields() if (paginationData.currentPage === 1) { getTableData() } paginationData.currentPage = 1}// 刷新表格const handleRefresh = () => { getTableData()}//#endregion/** 监听分页参数的变化 */// 看到这里我们就明白为什么查询和重置页面为什么跳转到第一页就不管了,因为这里在监听着分页参数的变化,这样的完美代码看着是很爽的,为作者点一个大赞watch([() => paginationData.currentPage, () => paginationData.pageSize], getTableData, { immediate: true })</script><template> <div class="app-container"> <el-card v-loading="loading" shadow="never" class="search-wrapper"> <el-form ref="searchFormRef" :inline="true" :model="searchData"> <el-form-item prop="username" label="用户名"> <el-input v-model="searchData.username" placeholder="请输入" /> </el-form-item> <el-form-item prop="phone" label="手机号"> <el-input v-model="searchData.phone" placeholder="请输入" /> </el-form-item> <el-form-item> <el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button> <el-button :icon="Refresh" @click="resetSearch">重置</el-button> </el-form-item> </el-form> </el-card> <el-card v-loading="loading" shadow="never"> <div class="toolbar-wrapper"> <div> <el-button type="primary" :icon="CirclePlus" @click="dialogVisible = true">新增用户</el-button> <el-button type="danger" :icon="Delete">批量删除</el-button> </div> <div> <el-tooltip content="下载"> <el-button type="primary" :icon="Download" circle /> </el-tooltip> <el-tooltip content="刷新表格"> <el-button type="primary" :icon="RefreshRight" circle @click="handleRefresh" /> </el-tooltip> </div> </div> <div class="table-wrapper"> <el-table :data="tableData"> <el-table-column type="selection" width="50" align="center" /> <el-table-column prop="username" label="用户名" align="center" /> <el-table-column prop="roles" label="角色" align="center"> <template #default="scope"> <el-tag v-if="scope.row.roles === 'admin'" effect="plain">admin</el-tag> <el-tag v-else type="warning" effect="plain">{{ scope.row.roles }}</el-tag> </template> </el-table-column> <el-table-column prop="phone" label="手机号" align="center" /> <el-table-column prop="email" label="邮箱" align="center" /> <el-table-column prop="status" label="状态" align="center"> <template #default="scope"> <el-tag v-if="scope.row.status" type="success" effect="plain">启用</el-tag> <el-tag v-else type="danger" effect="plain">禁用</el-tag> </template> </el-table-column> <el-table-column prop="createTime" label="创建时间" align="center" /> <el-table-column fixed="right" label="操作" width="150" align="center"> <template #default="scope"> <el-button type="primary" text bg size="small" @click="handleUpdate(scope.row)">修改</el-button> <el-button type="danger" text bg size="small" @click="handleDelete(scope.row)">删除</el-button> </template> </el-table-column> </el-table> </div> <div class="pager-wrapper"> <el-pagination background :layout="paginationData.layout" :page-sizes="paginationData.pageSizes" :total="paginationData.total" :page-size="paginationData.pageSize" :currentPage="paginationData.currentPage" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </el-card> <!-- 新增/修改 --> <el-dialog v-model="dialogVisible" :title="currentUpdateId === undefined ? '新增用户' : '修改用户'" @close="resetForm" width="30%" > <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" label-position="left"> <el-form-item prop="username" label="用户名"> <el-input v-model="formData.username" placeholder="请输入" /> </el-form-item> <el-form-item prop="password" label="密码"> <el-input v-model="formData.password" placeholder="请输入" /> </el-form-item> </el-form> <template #footer> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="handleCreate">确认</el-button> </template> </el-dialog> </div></template><style lang="scss" scoped>.search-wrapper { margin-bottom: 20px; :deep(.el-card__body) { padding-bottom: 2px; }}.toolbar-wrapper { display: flex; justify-content: space-between; margin-bottom: 20px;}.table-wrapper { margin-bottom: 20px;}.pager-wrapper { display: flex; justify-content: flex-end;}</style>分页数据作者是怎么处理的 usePagination

这是分页数据接口规范以及方法定义导出

import { reactive } from "vue"// ts 定义接口 -- 分页数据接口规范interface IDefaultPaginationData { total: number currentPage: number pageSizes: number[] pageSize: number layout: string}// ts 定义接口 -- 合并数据接口规范interface IPaginationData { total?: number currentPage?: number pageSizes?: number[] pageSize?: number layout?: string}/** 默认的分页参数 */const defaultPaginationData: IDefaultPaginationData = { total: 0, currentPage: 1, pageSizes: [10, 20, 50], pageSize: 10, layout: "total, sizes, prev, pager, next, jumper"}export function usePagination(_paginationData: IPaginationData = {}) { /** 合并分页参数 */ // Object.assign()是对象的静态方法,可以用来复制对象的可枚举属性到目标对象,利用这个特性可以实现对象属性的合并 // 意思就是传过来的值有的话就覆盖,没有就使用默认分页数据,这个处理很完美 const paginationData = reactive(Object.assign({ ...defaultPaginationData }, _paginationData)) /** 改变当前页码 */ const handleCurrentChange = (value: number) => { paginationData.currentPage = value } /** 改变每页显示数据数量 */ const handleSizeChange = (value: number) => { paginationData.pageSize = value } return { paginationData, handleCurrentChange, handleSizeChange }}}顺藤摸瓜找到 api 接口的封装

我们顺着上面发起请求的导出方法找到了这里(这里位于 src 下的 api 文件夹),这是一些简单的接口定义以及 api 接口的封装,等等,好像有一个奇怪的东西,在依赖包中使用的是 axios ,怎么出现了 request ,肯定还有一个整体封装层,并且应该在那里会有一个请求响应拦截器,我们去看看

import { request } from "@/utils/service"interface ICreateTableRequestData { username: string password: string}interface IUpdateTableRequestData { id: string username: string password?: string}interface IGetTableRequestData { /** 当前页码 */ currentPage: number /** 查询条数 */ size: number /** 查询参数 */ username?: string phone?: string}type GetTableResponseData = IApiResponseData<{ list: { createTime: string email: string id: string phone: string roles: string status: boolean username: string }[] total: number}>/** 增 */export function createTableDataApi(data: ICreateTableRequestData) { return request({ url: "table", method: "post", data })}/** 删 */export function deleteTableDataApi(id: string) { return request({ url: `table/${id}`, method: "delete" })}/** 改 */export function updateTableDataApi(data: IUpdateTableRequestData) { return request({ url: "table", method: "put", data })}/** 查 */export function getTableDataApi(params: IGetTableRequestData) { return request<GetTableResponseData>({ url: "table", method: "get", params })}api 接口再往底层找全局请求封装与请求拦截器 service.ts

果然是 😎

import axios, { type AxiosInstance, type AxiosRequestConfig } from "axios"import { useUserStoreHook } from "@/store/modules/user"import { ElMessage } from "element-plus"import { get } from "lodash-es"import { getToken } from "./cache/cookies"/** 创建请求实例 */function createService() { // 创建一个 Axios 实例 const service = axios.create() // 请求拦截 service.interceptors.request.use( (config) => config, // 发送失败 (error) => Promise.reject(error) ) // 响应拦截(可根据具体业务作出相应的调整) service.interceptors.response.use( (response) => { // apiData 是 API 返回的数据 const apiData = response.data as any // 这个 Code 是和后端约定的业务 Code const code = apiData.code // 如果没有 Code, 代表这不是项目后端开发的 API if (code === undefined) { ElMessage.error("非本系统的接口") return Promise.reject(new Error("非本系统的接口")) } else { switch (code) { case 0: // code === 0 代表没有错误 return apiData default: // 不是正确的 Code ElMessage.error(apiData.message || "Error") return Promise.reject(new Error("Error")) } } }, (error) => { // Status 是 HTTP 状态码 const status = get(error, "response.status") switch (status) { case 400: error.message = "请求错误" break case 401: // Token 过期时,直接退出登录并强制刷新页面(会重定向到登录页) useUserStoreHook().logout() location.reload() break case 403: error.message = "拒绝访问" break case 404: error.message = "请求地址出错" break case 408: error.message = "请求超时" break case 500: error.message = "服务器内部错误" break case 501: error.message = "服务未实现" break case 502: error.message = "网关错误" break case 503: error.message = "服务不可用" break case 504: error.message = "网关超时" break case 505: error.message = "HTTP 版本不受支持" break default: break } ElMessage.error(error.message) return Promise.reject(error) } ) return service}/** 创建请求方法 */function createRequestFunction(service: AxiosInstance) { return function <T>(config: AxiosRequestConfig): Promise<T> { const configDefault = { headers: { // 携带 Token Authorization: "Bearer " + getToken(), "Content-Type": get(config, "headers.Content-Type", "application/json") }, timeout: 5000, baseURL: import.meta.env.VITE_BASE_API, data: {} } return service(Object.assign(configDefault, config)) }}/** 用于网络请求的实例 */export const service = createService()/** 用于网络请求的方法 */export const request = createRequestFunction(service)
本文链接地址:https://www.jiuchutong.com/zhishi/297764.html 转载请保留说明!

上一篇:WGAN(Wasserstein GAN)看这一篇就够啦,WGAN论文解读

下一篇:uniapp中获取dom元素的方法,更改dom元素颜色(遇坑记录)(uniapp dom操作)

  • 路由器管理ip地址怎么获取(路由器管理ip)(路由器管理ip地址)

    路由器管理ip地址怎么获取(路由器管理ip)(路由器管理ip地址)

  • goodnotes怎么分屏做笔记(goodnotes怎么分屏写笔记)

    goodnotes怎么分屏做笔记(goodnotes怎么分屏写笔记)

  • 华为手机怎么设置分屏模式(华为手机怎么设置熄灭屏幕时间)

    华为手机怎么设置分屏模式(华为手机怎么设置熄灭屏幕时间)

  • oppofindx2pro有双频GPS的吗(oppofindx2pro有双WiFi)

    oppofindx2pro有双频GPS的吗(oppofindx2pro有双WiFi)

  • win10更新右键没有卸载(win10更新右键没有卸载选项)

    win10更新右键没有卸载(win10更新右键没有卸载选项)

  • 苹果11突然没信号(苹果11突然没信号怎么办)

    苹果11突然没信号(苹果11突然没信号怎么办)

  • 微信红包怎么指定人抢(微信红包怎么指定一个人领)

    微信红包怎么指定人抢(微信红包怎么指定一个人领)

  • 微信三天可见是对所有人的吗(微信三天可见是按小时算吗)

    微信三天可见是对所有人的吗(微信三天可见是按小时算吗)

  • 微信公众号保存的文章在哪里(微信公众号保存图片会被知道吗)

    微信公众号保存的文章在哪里(微信公众号保存图片会被知道吗)

  • 充电宝电池容量和额定容量的区别(充电宝电池容量和额定容量看哪个)

    充电宝电池容量和额定容量的区别(充电宝电池容量和额定容量看哪个)

  • 轻薄本256g够用吗(256g的笔记本电脑)

    轻薄本256g够用吗(256g的笔记本电脑)

  • 网络不稳定跟路由器有关系吗(网络不稳定跟路由器缓存有关系吗)

    网络不稳定跟路由器有关系吗(网络不稳定跟路由器缓存有关系吗)

  • 抖音视频不见了是什么意思(抖音视频不见了怎么回事 也没给我发信息)

    抖音视频不见了是什么意思(抖音视频不见了怎么回事 也没给我发信息)

  • xr充电到80就不动了(xr充电到80就不动了,关闭开关也不行)

    xr充电到80就不动了(xr充电到80就不动了,关闭开关也不行)

  • 图计算解决什么问题(图计算的应用)

    图计算解决什么问题(图计算的应用)

  • 微信发送视频大小限制(微信发送视频大小不能超过25M)

    微信发送视频大小限制(微信发送视频大小不能超过25M)

  • ipad pro无法充电(ipad pro无法充电,重启可以)

    ipad pro无法充电(ipad pro无法充电,重启可以)

  • ios怎么连接手柄(ios连接手柄没反应)

    ios怎么连接手柄(ios连接手柄没反应)

  • 屏幕保护密码需要大小写吗(屏幕保护开机密码)

    屏幕保护密码需要大小写吗(屏幕保护开机密码)

  • 抖音刷过的视频怎么查找(抖音刷过的视频有记录吗)

    抖音刷过的视频怎么查找(抖音刷过的视频有记录吗)

  • 华为mate30有听筒吗(华为mate30听筒坏了,修理费多少钱)

    华为mate30有听筒吗(华为mate30听筒坏了,修理费多少钱)

  • 鲁通卡怎么缴费(鲁通卡如何缴费)

    鲁通卡怎么缴费(鲁通卡如何缴费)

  • 苹果11有没有小圆点(苹果11有没有小圆点功能)

    苹果11有没有小圆点(苹果11有没有小圆点功能)

  • 华为智慧能力有用吗(华为智慧能力有什么用)

    华为智慧能力有用吗(华为智慧能力有什么用)

  • 小米手环3和4腕带通用吗(小米手环3和4腕带能不能互用)

    小米手环3和4腕带通用吗(小米手环3和4腕带能不能互用)

  • 怎样设置电话正在通话中(怎样设置电话正在通话中苹果手机)

    怎样设置电话正在通话中(怎样设置电话正在通话中苹果手机)

  • vivox27可以快充吗(vivox27手机快充怎么开启功能在哪里)

    vivox27可以快充吗(vivox27手机快充怎么开启功能在哪里)

  • Vue 组件强制刷新方式(vue组件强制刷新)

    Vue 组件强制刷新方式(vue组件强制刷新)

  • 加计扣除填不了
  • 退税进度显示国库退库失败怎么办 是什么原因
  • 什么是税法要素
  • 法人工资怎么发最划算
  • 个税抵扣夫妻双方只要一个人填写吗
  • 税务报表的利润表
  • 停车场自助缴费系统
  • 企业赠送礼品是否涉税
  • 现金折让怎么做会计分录
  • 企业资产净资产
  • 固定资产一次性扣除政策2023
  • 公司买理财产品的账务处理
  • 建筑业营改增账务处理怎么做?
  • 小规模季度超过30万怎么填报增值税
  • 货代企业所得税优惠政策
  • 增值税发票发票号
  • 上年免税收入转内销补交税分录
  • 奖励费收入是什么
  • 教育费附加免征还计提吗
  • 结转本期损益要过账吗
  • vcpkgsrv.exe是什么进程
  • 经营租赁固定资产体现实质重于形式
  • 小规模纳税人多少钱不用交税
  • 图文详解:台盆柜安装的全过程
  • php限制登录次数
  • 其他业务支出的二级科目有哪些
  • 利息增值税及附加计算
  • yolov3目标检测步骤流程图
  • cssborder虚线边框
  • 面试学弟学妹问题
  • websocket怎么用
  • sed命令大全
  • 记账凭证的记账符号要打勾吗
  • 劳务派遣怎么开
  • 对公账号可以绑定微信提现吗
  • python @time
  • 没有购销合同的原因有哪些
  • 固定资产的弃置费用
  • 可以直接在企业所得税税前扣除的是
  • PostgreSQL教程(十六):系统视图详解
  • 住房公积金中的钱可以当首付吗?
  • 房屋租赁合同印花税怎么交,一年一次吗
  • 业务招待费如何调整
  • 无形资产的意思是
  • 合伙企业是否需要缴纳印花税
  • 广告类的公司
  • 暂估入账的固定资产
  • 电费可以计入营业外收入吗
  • 银行账户收费开户流程
  • 确认收入时,也必须确认资产或债务
  • 人力资源劳务费计入什么科目
  • 银行回单nxt
  • 在我国土地使用权分为哪几类
  • 工程项目立项前包括哪几个过程
  • 建筑企业合理避税
  • 预收冲应收怎么做账
  • 停薪留职社保怎么算
  • mysql5.7压缩包
  • 怎么把操作系统转移到另一个盘
  • mssecsvr.exe病毒现象
  • final cut pro能破解吗
  • macbook的qq怎么更换背景
  • linux本地解析
  • win7系统安装软件乱码
  • win8怎么调整屏幕分辨率
  • Win10年度更新将统一Insider与Xbox One预览体验
  • win7 svchost
  • Cocos2dx3.2 Crazy Tetris 绘制不规则方块 遮罩(ClippingNode的使用)
  • linux安装unixodbc
  • innertext赋值
  • Python+Wordpress制作小说站
  • js实现弹窗
  • javascript类的继承
  • android布局文件放在哪
  • javascript图片
  • js中布尔值为false的六种情况
  • 电子发票如何盖电子章
  • 沈阳市税务局最新公告
  • 国家税务总局两江新区税务局
  • 个体经营所得申报
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设