• 售前

  • 售后

热门帖子
入门百科

Vue组件封装上传图片和视频的示例代码

[复制链接]
计划你大爷计j 显示全部楼层 发表于 2021-8-14 14:39:44 |阅读模式 打印 上一主题 下一主题

首先下载依靠:
  1. cnpm i -S vue-uuid ali-oss
复制代码
图片和视频字段都是数组范例,包管可以上传多个文件。
UploadImageVideo:
  1. <!--UploadImageVideo 分片上传    -->
  2. <template>
  3.   <div class="UploadImageVideo">
  4.     <el-upload
  5.       action
  6.       :on-change="handleChange"
  7.       :on-remove="handleRemove"
  8.       :limit="limitFileNumber"
  9.       :on-exceed="handleExceed"
  10.       :file-list="_fileList"
  11.       :http-request="handleHttpRequest"
  12.       :before-upload="handleBeforeUpload"
  13.       :multiple="isMultiple"
  14.     >
  15.       <el-button slot="trigger" size="small" type="primary">选择文件</el-button>
  16.       <div slot="tip" class="el-upload__tip">{{ tip }}</div>
  17.     </el-upload>
  18.     <el-dialog
  19.       title="上传进度"
  20.       :visible.sync="dialogTableVisible"
  21.       :close-on-click-modal="false"
  22.       :modal-append-to-body="false"
  23.     >
  24.       <el-progress :text-inside="true" :stroke-width="26" :percentage="percentage"></el-progress>
  25.     </el-dialog>
  26.   </div>
  27. </template>
  28. <script>
  29. import { uuid } from "vue-uuid";
  30. const OSS = require("ali-oss");
  31. export default {
  32.   name: "",
  33.   components: {},
  34.   props: {
  35.     region: {
  36.       type: String,
  37.       default: "oss-cn-chengdu"
  38.     },
  39.     accessKeyId: {
  40.       type: String,
  41.       default: "xxx"
  42.     },
  43.     accessKeySecret: {
  44.       type: String,
  45.       default: "xxx"
  46.     },
  47.     //存储位置
  48.     bucket: {
  49.       type: String,
  50.       required: true
  51.     },
  52.     currentUrls: {
  53.       type: Array,
  54.       default: () => [],
  55.       required: true
  56.     },
  57.     //限制上传文件数量
  58.     limitFileNumber: {
  59.       type: Number,
  60.       default: 1
  61.     },
  62.     //是否支持多选
  63.     isMultiple: {
  64.       type: Boolean,
  65.       default: false
  66.     },
  67.     //文件格式
  68.     fileType: {
  69.       type: String,
  70.       default: ""
  71.     },
  72.     //提示
  73.     tip: {
  74.       type: String
  75.     }
  76.   },
  77.   data() {
  78.     return {
  79.       client: new OSS({
  80.         region: this.region,
  81.         accessKeyId: this.accessKeyId,
  82.         accessKeySecret: this.accessKeySecret,
  83.         bucket: this.bucket
  84.       }),
  85.       percentage: 0,
  86.       dialogTableVisible: false,
  87.       fileList: []
  88.     };
  89.   },
  90.   computed: {
  91.     //注意:计算属性里面慎用console.log()来打印,因为有可能打印的变量是依赖某个属性而出现该计算属性重复调用!!!!!!
  92.     _fileList() {
  93.       const arr = [];
  94.       //一定要this.currentUrls判断一下是否非空,否则要报错
  95.       if (this.currentUrls.length !== 0) {
  96.         for (const item of this.currentUrls) {
  97.           let { pathname } = new URL(item);
  98.           arr.push({ name: decodeURIComponent(pathname), url: item });
  99.         }
  100.       }
  101.       this.fileList = arr; //这行代码很重要!!
  102.       return arr;
  103.     }
  104.   },
  105.   created() {},
  106.   mounted() {},
  107.   methods: {
  108.     handleChange(file, fileList) {
  109.       this.fileList = fileList;
  110.     },
  111.     handleRemove(file, fileList) {
  112.       this.fileList = fileList;
  113.     },
  114.     handleExceed(files, fileList) {
  115.       this.$message.warning(
  116.         `当前限制选择 ${this.limitFileNumber} 个文件,本次选择了 ${
  117.           files.length
  118.         } 个文件,共选择了 ${files.length + fileList.length} 个文件`
  119.       );
  120.     },
  121.     //注意:为了让自定义上传handleHttpRequest生效,需满足:
  122.     // 1、设置:auto-upload='true'或者不写这个属性,因为它默认为true  2、设置action='#'或者直接写action
  123.     handleHttpRequest(file) {
  124.       //虽然没有内容,但是这个函数不能少!
  125.     },
  126.     //注意:自定义上传handleHttpRequest必须要生效,才会触发before-upload钩子函数
  127.     handleBeforeUpload(file) {
  128.       if (this.fileType == "image") {
  129.         let { type, size, name } = file;
  130.         let isJPEG = type === "image/jpeg";
  131.         let isJPG = type === "image/jpg";
  132.         let isPNG = type === "image/png";
  133.         let isLt5M = size / 1024 / 1024 < 5;
  134.         if (!isJPEG && !isJPG && !isPNG) {
  135.           this.$message.error("上传图片只能是 JPEG/JPG/PNG  格式!");
  136.           return false;
  137.         }
  138.         if (!isLt5M) {
  139.           this.$message.error("单张图片大小不能超过 5MB!");
  140.           return false;
  141.         }
  142.       }
  143.       if (this.fileType == "video") {
  144.         let { type, size, name } = file;
  145.         let isMP4 = type === "video/mp4";
  146.         let isLt50M = size / 1024 / 1024 < 50;
  147.         if (!isMP4) {
  148.           this.$message.error("上传视频只能是 MP4  格式!");
  149.           return false;
  150.         }
  151.         if (!isLt50M) {
  152.           this.$message.error("单个视频大小不能超过 50MB!");
  153.           return false;
  154.         }
  155.       }
  156.     },
  157.     // 分片上传数据,可展示进度条。上传重命名后的文件到alioss, 并返回单个文件url字符串。可支持中文文件名
  158.     async UploadImageVideo(filename, file) {
  159.       let newFileName =
  160.         filename.split(".")[0] + "-" + uuid.v1() + "." + filename.split(".")[1];
  161.       let that = this;
  162.       that.dialogTableVisible = true;
  163.       let {
  164.         res: { requestUrls }
  165.       } = await this.client.multipartUpload(newFileName, file, {
  166.         progress: function(p, checkpoint) {
  167.           that.percentage = parseFloat((p * 100).toFixed(2));
  168.         }
  169.       });
  170.       if (that.percentage == 100) {
  171.         that.dialogTableVisible = false;
  172.       }
  173.       let { origin, pathname } = new URL(requestUrls[0]);
  174.       return origin + decodeURIComponent(pathname);
  175.     },
  176.     //批量上传文件。返回成功上传的url数组
  177.     async addFiles() {
  178.       let urls = [];
  179.       if (this.fileList.length !== 0) {
  180.         for (const item of this.fileList) {
  181.           let { name, raw } = item;
  182.           let pathname = await this.UploadImageVideo(name, raw);
  183.           urls.push(pathname);
  184.         }
  185.       }
  186.       return urls;
  187.     },
  188.     //更新文件数据。上传新数据到服务器,并删除服务器中的旧数据,返回更新后的url数组
  189.     async UpdateFiles() {
  190.       let arr_newUploaded = []; //新上传的图片url。
  191.       let arr_original = []; //原有的图片url。不用删除
  192.       let arr_delete = []; //需要删除的图片url。
  193.       if (this.fileList.length !== 0) {
  194.         for (const { raw, name, url } of this.fileList) {
  195.           //注意:这里一定要判断raw是否存在。存在,则表示是新上传的;不存在,则表示是原有的
  196.           if (raw) {
  197.             let pathname = await this.UploadImageVideo(name, raw);
  198.             arr_newUploaded.push(pathname);
  199.           }
  200.           if (this.currentUrls.includes(url)) {
  201.             arr_original.push(url);
  202.           }
  203.         }
  204.       }
  205.       for (const element of this.currentUrls) {
  206.         if (!arr_original.includes(element)) {
  207.           arr_delete.push(element);
  208.         }
  209.       }
  210.       await this.deleteMultiFiles(arr_delete);
  211.       return [...arr_original, ...arr_newUploaded];
  212.     },
  213.     //批量删除服务器中的文件。参数:待删除到服务器文件url数组。
  214.     async deleteMultiFiles(urls = []) {
  215.       let arr_pathname = [];
  216.       if (urls.length !== 0) {
  217.         for (const item of urls) {
  218.           //不要用let url=require("url");url.parse();已失效。要用new URL()
  219.           let { pathname } = new URL(item);
  220.           // decodeURIComponent()函数将中文乱码转为中文
  221.           arr_pathname.push(decodeURIComponent(pathname));
  222.         }
  223.         //删除服务器中的图片
  224.         await this.client.deleteMulti(arr_pathname);
  225.       }
  226.     }
  227.   },
  228.   watch: {}
  229. };
  230. </script>
  231. <style lang="scss" scoped>
  232. .UploadImageVideo {
  233.   /*去除upload组件过渡效果*/
  234.   ::v-deep .el-upload-list__item {
  235.     transition: none !important;
  236.   }
  237. }
  238. </style>
复制代码
利用:
  1. <UploadImageVideo
  2.   ref="ref_UploadImageVideo"
  3.   bucket="xxx"
  4.   :currentUrls="formData.imgurl"
  5.   :limitFileNumber="3"
  6.   tip="1、最多上传3张照片; 2、上传图片只能是 JPEG/JPG/PNG 格式; 3、单张图片大小不能超过 5MB!"
  7.   fileType="image"
  8.   :isMultiple="true"
  9. ></UploadImageVideo>
复制代码
       
  • fileType可选。默认不写,表示图片、视频都可上传。fileType="image"表示只能上传图片。fileType="video"表示只能上传视频   
  • bucket必选。   
  • isMultiple可选。默认为false   
  • currentUrls必选。当前目前已有的文件服务器url数组。通常新增文件时,传入的currentUrls为空数组[];更新文件时,传入到currentUrls为非空数组   
  • tip可选。提示内容
提供的方法:(当前组件中全部的上传都是批量上传,且为分片上传,以展示上传进度条)
       
  • UpdateFiles()。更新文件数据。上传新数据到服务器,并删除服务器中的旧数据,返回更新后的url数组   
  • addFiles()。批量上传文件。返回乐成上传的url数组   
  • deleteMultiFiles(urls = [])。批量删除服务器中的文件。参数:待删除到服务器文件url数组。   
  • UploadImageVideo(filename, file)。分片上传数据,可展示进度条。上传重命名后的文件到alioss, 并返回单个文件url字符串。可支持中文文件名
  1. 调用组件中的方法:例如可通过 <strong>let urls = await this.$refs["ref_UploadImageVideo"].addFiles();调用批量上传图片或视频的方法</strong>
复制代码
例1:
  
  1. <!--userManage-->
  2. <template>
  3.   <div class="userManage">
  4.     <el-card>
  5.       <div style="margin-bottom: 10px">
  6.         <el-input
  7.           v-model="searchName"
  8.           clearable
  9.           placeholder="输入用户名称搜索"
  10.           style="width: 200px; margin-right: 10px"
  11.         />
  12.         <el-button
  13.           sizi="mini"
  14.           type="success"
  15.           icon="el-icon-search"
  16.           @click="searchUser(searchName)"
  17.         >搜索</el-button>
  18.         <el-button
  19.           sizi="mini"
  20.           type="warning"
  21.           icon="el-icon-refresh-left"
  22.           @click="searchName = ''"
  23.         >重置</el-button>
  24.         <el-button sizi="mini" @click="handleAdd()" type="primary" icon="el-icon-plus">新增</el-button>
  25.         <el-button @click="getUserList()" sizi="mini" icon="el-icon-refresh" style="float: right">刷新</el-button>
  26.       </div>
  27.       <el-table :data="tableData" border v-loading="isLoading">
  28.         <el-table-column label="用户名" prop="username" align="center" width="150px"></el-table-column>
  29.         <el-table-column label="密码" prop="password" align="center"></el-table-column>
  30.         <el-table-column label="图片" align="center">
  31.           <template slot-scope="scope">
  32.             <div
  33.               style="
  34.                 display: flex;
  35.                 justify-content: space-around;
  36.                 flex-flow: row wrap;
  37.               "
  38.             >
  39.               <el-image
  40.                 style="width: 50px; height: 50px"
  41.                 v-for="(item, index) in scope.row.imgurl"
  42.                 :key="index"
  43.                 :src="item"
  44.                 :preview-src-list="scope.row.imgurl"
  45.               ></el-image>
  46.               <!-- <a :href="scope.row.imgurl" rel="external nofollow"  target="_blank">{{scope.row.imgurl}}</a> -->
  47.             </div>
  48.           </template>
  49.         </el-table-column>
  50.         <el-table-column label="操作" align="center">
  51.           <template slot-scope="scope">
  52.             <el-button size="mini" @click="showEditDialog(scope.row)">
  53.               <i class="el-icon-edit" /> 编辑
  54.             </el-button>
  55.             <el-button size="mini" type="danger" @click="handleDelete(scope.row)">
  56.               <i class="el-icon-delete" /> 删除
  57.             </el-button>
  58.           </template>
  59.         </el-table-column>
  60.       </el-table>
  61.     </el-card>
  62.     <UserManageDialog :dialog="dialog" :formData="formData" @addUser="addUser" @editUser="editUser"></UserManageDialog>
  63.   </div>
  64. </template>
  65. <script>
  66. import UserManageDialog from "./userManageDialog.vue";
  67. import { client_alioss, deleteMultiFiles } from "@/utils/alioss.js";
  68. import {
  69.   addUser,
  70.   getUserList,
  71.   editUser,
  72.   deleteUser,
  73.   searchUser
  74. } from "@/api/userManage/index";
  75. export default {
  76.   name: "userManage",
  77.   components: { UserManageDialog },
  78.   data() {
  79.     return {
  80.       searchName: "",
  81.       isLoading: false,
  82.       dialog: {
  83.         show: false,
  84.         title: ""
  85.       },
  86.       formData: {},
  87.       tableData: [
  88.         {
  89.           _id: "",
  90.           username: "admin",
  91.           password: "123",
  92.           imgurl: []
  93.         }
  94.       ],
  95.       currentImgs: []
  96.     };
  97.   },
  98.   props: {},
  99.   created() {},
  100.   mounted() {
  101.     this.getUserList();
  102.   },
  103.   computed: {},
  104.   methods: {
  105.     //获取用户列表
  106.     async getUserList() {
  107.       this.isLoading = true;
  108.       let { data } = await getUserList();
  109.       this.tableData = data.data;
  110.       this.isLoading = false;
  111.     },
  112.     //打开新增用户窗口
  113.     handleAdd() {
  114.       this.dialog = {
  115.         show: true,
  116.         title: "新增用户",
  117.         option: "add"
  118.       };
  119.       this.formData = {
  120.         username: "",
  121.         password: "",
  122.         imgurl: []
  123.       };
  124.     },
  125.     //打开编辑用户窗口
  126.     showEditDialog(row) {
  127.       this.currentImgs = row.imgurl;
  128.       this.dialog = {
  129.         show: true,
  130.         title: "编辑用户",
  131.         option: "edit"
  132.       };
  133.       this.formData = {
  134.         _id: row._id,
  135.         username: row.username,
  136.         password: row.password,
  137.         imgurl: row.imgurl
  138.       };
  139.     },
  140.     //新增用户
  141.     async addUser(urls) {
  142.       this.formData.imgurl = urls;
  143.       await addUser(this.formData);
  144.       this.dialog.show = false;
  145.       this.$notify({
  146.         title: "成功",
  147.         message: "新增用户成功!",
  148.         type: "success"
  149.       });
  150.       this.getUserList();
  151.     },
  152.     //编辑用户
  153.     async editUser(urls) {
  154.       this.formData.imgurl = urls;
  155.       await editUser(this.formData, this.formData._id); //更新数据库,尤其是图片url
  156.       this.dialog.show = false;
  157.       this.$notify({
  158.         title: "成功",
  159.         message: "编辑用户成功!",
  160.         type: "success"
  161.       });
  162.       this.getUserList();
  163.     },
  164.     //删除用户
  165.     handleDelete({ _id }) {
  166.       this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {
  167.         confirmButtonText: "确定",
  168.         cancelButtonText: "取消",
  169.         type: "warning"
  170.       })
  171.         .then(async () => {
  172.           this.$message({
  173.             type: "success",
  174.             message: "删除成功!",
  175.             showClose: true
  176.           });
  177.           let {
  178.             data: { imgurl }
  179.           } = await deleteUser(_id);
  180.           //删除服务器中的文件。传入待删除的url数组
  181.           await deleteMultiFiles(imgurl);
  182.           this.getUserList();
  183.         })
  184.         .catch(() => {
  185.           this.$message({
  186.             type: "info",
  187.             message: "已取消删除",
  188.             showClose: true
  189.           });
  190.         });
  191.     },
  192.     //根据用户名查询
  193.     async searchUser(searchName) {
  194.       this.isLoading = true;
  195.       let { data } = await searchUser({
  196.         searchName
  197.       });
  198.       this.tableData = data.data;
  199.       this.isLoading = false;
  200.     }
  201.   },
  202.   watch: {}
  203. };
  204. </script>
  205. <style lang="scss" scoped>
  206. .userManage {
  207. }
  208. </style>
复制代码
  1. <!--userManageDialog   -->
  2. <template>
  3.   <div class="userManageDialog">
  4.     <el-dialog :title="dialog.title" width="45%" :visible.sync="dialog.show" v-if="dialog.show">
  5.       <el-form ref="ref_form_userManage" :model="formData" :rules="rules" label-width="100px">
  6.         <el-form-item label="用户名" prop="username">
  7.           <el-input v-model="formData.username" autocomplete="off" style="width: 90%"></el-input>
  8.         </el-form-item>
  9.         <el-form-item label="密码" prop="password">
  10.           <el-input v-model="formData.password" autocomplete="off" style="width: 90%"></el-input>
  11.         </el-form-item>
  12.         <el-form-item label="图片" prop="imgurl">
  13.           <!-- fileType属性不写的话,表示图片、视频都可上传。fileType="image"表示只能上传图片。fileType="video"表示只能上传视频 -->
  14.           <UploadImageVideo
  15.             ref="ref_UploadImageVideo"
  16.             bucket="bucket-lijiang-test"
  17.             :currentUrls="formData.imgurl"
  18.             :limitFileNumber="3"
  19.             tip="1、最多上传3张照片; 2、上传图片只能是 JPEG/JPG/PNG 格式; 3、单张图片大小不能超过 5MB!"
  20.             fileType="image"
  21.             :isMultiple="true"
  22.           ></UploadImageVideo>
  23.         </el-form-item>
  24.       </el-form>
  25.       <div slot="footer" class="dialog-footer">
  26.         <el-button @click="dialog.show = false">取 消</el-button>
  27.         <el-button
  28.           v-if="dialog.option == 'add'"
  29.           @click="addUser('ref_form_userManage')"
  30.           type="primary"
  31.         >确 定</el-button>
  32.         <el-button
  33.           v-if="dialog.option == 'edit'"
  34.           @click="editUser('ref_form_userManage')"
  35.           type="primary"
  36.         >确 定</el-button>
  37.       </div>
  38.     </el-dialog>
  39.   </div>
  40. </template>
  41. <script>
  42. import UploadImageVideo from "@/components/UploadImageVideo";
  43. export default {
  44.   name: "userManageDialog",
  45.   components: { UploadImageVideo },
  46.   props: ["dialog", "formData"],
  47.   data() {
  48.     return {
  49.       fileList: [],
  50.       rules: {
  51.         username: [
  52.           { required: true, message: "请输入用户名称", trigger: "blur" }
  53.         ]
  54.       }
  55.     };
  56.   },
  57.   created() {},
  58.   mounted() {},
  59.   computed: {},
  60.   methods: {
  61.     addUser(formName) {
  62.       this.$refs[formName].validate(async valid => {
  63.         if (valid) {
  64.           let urls = await this.$refs["ref_UploadImageVideo"].addFiles();
  65.           this.$emit("addUser", urls);
  66.         } else {
  67.           console.log("error submit!!");
  68.           return false;
  69.         }
  70.       });
  71.     },
  72.     editUser(formName) {
  73.       this.$refs[formName].validate(async valid => {
  74.         if (valid) {
  75.           let urls = await this.$refs["ref_UploadImageVideo"].UpdateFiles();
  76.           this.$emit("editUser", urls);
  77.         } else {
  78.           console.log("error submit!!");
  79.           return false;
  80.         }
  81.       });
  82.     }
  83.   },
  84.   watch: {}
  85. };
  86. </script>
  87. <style lang="scss" scoped>
  88. </style>
复制代码
  1. import { uuid } from 'vue-uuid';
  2. const OSS = require("ali-oss");
  3. let client = new OSS({
  4.     region: "oss-cn-chengdu",
  5.     accessKeyId: "LTAI5tQPHvixV8aakp1vg8Jr",
  6.     accessKeySecret: "xYyToToPe8UFQMdt4hpTUS4PNxzl9S",
  7.     bucket: "bucket-lijiang-test",
  8. });
  9. export const client_alioss = client;
  10. //删除文件数组
  11. export async function deleteMultiFiles(urls = []) {
  12.     let arr_pathname = [];
  13.     if (urls.length !== 0) {
  14.         for (const item of urls) {
  15.             //不要用let url=require("url");url.parse();已失效。要用new URL()
  16.             let { pathname } = new URL(item);
  17.             // decodeURIComponent()函数将中文乱码转为中文
  18.             arr_pathname.push(decodeURIComponent(pathname));
  19.         }
  20.         await client.deleteMulti(arr_pathname);
  21.     }
  22. }
复制代码
  1. import request from '@/utils/request'
  2. //  获取用户列表
  3. export function getUserList() {
  4.     return request({
  5.         url: '/api/userManage',
  6.         method: 'get'
  7.     })
  8. }
  9. //  新增用户
  10. export function addUser(data) {
  11.     return request({
  12.         url: '/api/userManage',
  13.         method: 'post',
  14.         data
  15.     })
  16. }
  17. //  编辑用户
  18. export function editUser(data, _id) {
  19.     return request({
  20.         url: `/api/userManage/${_id}`,
  21.         method: 'put',
  22.         data
  23.     })
  24. }
  25. //  删除用户
  26. export function deleteUser(_id) {
  27.     return request({
  28.         url: `/api/userManage/${_id}`,
  29.         method: 'delete'
  30.     })
  31. }
  32. //  根据关键字查询
  33. export function searchUser(data) {
  34.     return request({
  35.         url: `/api/userManage/search`,
  36.         method: 'get',
  37.         params: data
  38.     })
  39. }
复制代码
  
  1. const router = require('koa-router')()
  2. const User = require("../models/User"); //引入模块模型
  3. router.prefix('/userManage')
  4. //获取用户列表
  5. router.get('/', async (ctx, next) => {
  6.     let data = await User.find({})
  7.     ctx.body = {
  8.         code: 200,
  9.         message: "请求成功",
  10.         data,
  11.     }
  12. })
  13. //新增用户
  14. router.post('/', async (ctx, next) => {
  15.     let { username, password, imgurl } = ctx.request.body;
  16.     await User.create({ username, password, imgurl })
  17.     ctx.body = { code: 200, message: "新增成功" }
  18. })
  19. //编辑用户
  20. router.put('/:_id', async (ctx, next) => {
  21.     let { username, password, imgurl } = ctx.request.body;
  22.     let { _id } = ctx.params
  23.     await User.findByIdAndUpdate(_id, { username, password, imgurl })
  24.     ctx.body = { code: 200, message: "编辑成功" }
  25. })
  26. //删除用户
  27. router.delete('/:_id', async (ctx, next) => {
  28.     let { _id } = ctx.params;
  29.     let { imgurl } = await User.findByIdAndDelete(_id)
  30.     ctx.body = { code: 200, message: "删除成功", imgurl }
  31. })
  32. //根据关键字查询用户。模糊查询
  33. router.get('/search', async (ctx, next) => {
  34.     let { searchName } = ctx.request.query;
  35.     let data = await User.find({ username: { $regex: searchName } })
  36.     ctx.body = { code: 200, message: "查询成功", data }
  37. })
  38. module.exports = router
复制代码
到此这篇关于Vue封装上传图片和视频的组件的文章就先容到这了,更多干系vue组件封装内容请搜索脚本之家从前的文章或继承欣赏下面的干系文章渴望各人以后多多支持脚本之家!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作