主頁 > 前端設計 > Vue + element從零打造一個H5頁面可視化編輯器——pl-drag-template

Vue + element從零打造一個H5頁面可視化編輯器——pl-drag-template

2020-09-11 02:21:26 前端設計

pl-drag-template

Github地址:https://github.com/livelyPeng/pl-drag-template

前言

想必你一定使用過易企秀或百度H5等微場景生成工具制作過炫酷的h5頁面,除了感嘆其神奇之處有沒有想過其實作方式呢?本文從零開始實作一個H5編輯器專案完整設計思路和主要實作步驟,并開源前后端代碼,有需要的小伙伴可以按照該教程從零實作自己的H5編輯器,(實作起來并不復雜,該教程只是提供思路,并非最佳實踐)

一個h5可視化編輯器種子, 高仿凡科建站模板,

點擊查看pl-drag-template在線demo

大概圖形: image

拖動左邊組件到畫板區域釋放即可,或者點擊左邊區域的組件,

注意: 最好使用谷歌打開,點擊保存按鈕就是一串json資料,你可以吧這個資料拿到其他手機平臺進行渲染啦,有問題就加群 里面代碼注釋齊全,誰都看懂的哦

在這個模板的基礎上,你就可以實作類似凡科的模板(當然你還可以實作其他的類似模板),如下圖就是我們產品的模樣

image

專案目錄

 src {
     apiUrl: 請路徑存放
     assets: 專案資產存在(圖片等)
     components: 公用組件存放
     module: 模塊位置  {
         畫板模塊的配置如下: {
            components: 當前模塊的私有組件 {
              attributeConfig: 右邊屬性配置組件
              ... 其他的都是畫板頁面的組件
            }
            pluginLibrary: 畫板的插件/模塊/組件(非常重要)
            routers: 當前模塊的路由表
            style: 當前畫板的樣式
            utils: 公用js存放庫
            vuex: 當前模塊的狀態存盤
            viewPage: 當前模塊的頁面
            index.js: 匯出當前模塊
         }
     }
     vuex: 整個專案的狀態存盤匯集地方
     themes: 整個專案的公用樣式表集中地方
     utils: 整個專案的工具檔案夾
  }

 

技術堆疊

前端:
vue: 模塊化開發少不了angular,react,vue三選一,這里選擇了vue,
vuex: 狀態管理
less: css預編譯器,
element-ui:不造輪子,有現成的優秀的vue組件庫當然要用起來,沒有的自己再封裝一些就可以了,
loadsh:工具類

工程搭建

基于vue-cli2環境搭建

  • 如何規劃好我們專案的目錄結構?首先我們需要有一個目錄作為前端專案,一個目錄作為后端專案,所以我們要對vue-cli 生成的專案結構做一下改造:
···
·
|-- client                // 原 src 目錄,改成 client 用作前端專案目錄
|-- server                // 新增 server 用于服務端專案目錄
|-- engine-template        // 新增 engine-template 用于頁面模板庫目錄
|-- docs                // 新增 docs 預留撰寫專案檔案目錄
·
···

 

  • 這樣的話 我們需要再把我們webpack組態檔稍作一下調整

  • module.exports = {
      resolve: {
        extensions: ['.ts', '.js', '.vue', '.json'],
        alias: {
          // 'vue$': 'vue/dist/vue.esm.js',
          '@': utils.resolve('src')
        }
      },
      externals: {
        'vue': 'Vue',
        "echarts": "echarts",
        'vue-router': 'VueRouter',
        'vuex': 'Vuex',
        'element-ui': 'ELEMENT',
        'moment': 'moment'
      },
      module: {
        rules: [
          ...(config.dev.useEslint ? [createLintingRule()] : []),
          {
            test: /\.vue$/,
            loader: 'vue-loader',
            options: {
              transformAssetUrls: {
                video: ['src', 'poster'],
                source: 'src',
                img: 'src',
                image: 'xlink:href'
              }
            }
          }, {
            test: /\.js$/,
            loader: 'babel-loader',
            exclude: file => /node_modules/.test(file) && !/\.vue\.js/.test(file) && !/element-ui(\\|\/)(src|packages)/.test(file) && !/pl-table/.test(file)
          }, {
            test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: utils.assetsPath('img/[name].[hash].[ext]')
            }
          }, {
            test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: utils.assetsPath('media/[name].[hash].[ext]')
            }
          }, {
            test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: utils.assetsPath('fonts/[name].[hash].[ext]')
            }
          }, {
            test: /\.less$/,
            use: [{
              loader: process.env.NODE_ENV === 'production' ? MiniCssExtractPlugin.loader : 'vue-style-loader'
            }, {
              loader: 'css-loader',
              options: {
                sourceMap: cssSourceMap
              }
            }, {
              loader: 'less-loader',
              options: {
                sourceMap: cssSourceMap
              }
            }, {
              loader: 'sass-resources-loader',
              options: {
                resources: [
                  path.resolve(__dirname, '../src/themes/publicStyle/common.less')
                ]
              }
            }]
          }, {
            test: /\.css$/,
            use: [{
              loader: process.env.NODE_ENV === 'production' ? MiniCssExtractPlugin.loader : 'vue-style-loader',
            }, {
              loader: 'css-loader',
              options: {
                sourceMap: cssSourceMap
              }
            }]
          }]
      },
      plugins: [
        new VueLoaderPlugin(),
        // 復制靜態資源到目錄中,如果有更多需要復制的資源,請在這里添加
        new CopyWebpackPlugin([{
          from: utils.resolve('static'),
          to: config.build.assetsSubDirectory,
          ignore: ['.*']
        }])
      ]
    }

這樣我們搭建起來一個簡易的專案目錄結構,

前端編輯器實作

編輯器的實作思路是:編輯器生成頁面JSON資料,服務端負責存取JSON資料,渲染時從服務端取資料JSON交給前端模板處理,

 

資料結構(非常重要)

/*
 *   注意注意注意: pluginLibrary里面組件的name值必須寫,然后必須寫下面的elName組件名
 *   1. elName: 'pl-text', // 非常重要請正確寫上對應的vue組件的組件名,name值 如export default {name: 'PlButton'} 那么elName就是pl-button
 *   2. 除了容器的物件plContainer屬性,(注意:看容器的屬性請看下面的容器基本結構)其他配置表屬性的介紹如下
 *    title: 組件提示文字(左邊組件按鈕區域用到了)
 *    icon: 組件圖示(左邊組件按鈕區域用到了,使用的是 Iconfont-阿里巴巴矢量圖示庫)
 *    以下全是組件本身的屬性,不是左邊組件按鈕區域串列的屬性
 *    elName: 組件名
 *    pointList: 控制組件拖動的方向(拖動的小圓點)  pointList: ['lt' 左上, 'rt' 右上, 'lb' 左下, 'rb' 右下, 'l' 左, 'r' 右, 't' 上, 'b' 下],
 *     // ['lt', 'rt', 'lb', 'rb', 'l', 'r', 't', 'b' ]
 *    value: '' // 輸入框的值,主要用在這個畫板元素上的輸入框型別組件上
 *    contenteditable: 組件輸入狀態是否可以被拖動
 *    placeholder: 輸入框型別的組件,空文本提示文字
 *    commonStyle:初始化的樣式,就是css不多介紹
 *    options:{ // 組件配置項
 *        classList: [], 當前組件的類集合
          lineHeightChange: true // 表示行高需要隨著拖動的高度變化(只有可以拖動的元素有效)
 *    }
 *    module: boolean 為true代表當前組件不是個畫板元素,而是作為一個模塊的身份,(但是它依然存放在容器中) 什么是非畫板元素,就是不能再自由容器中拖動和自由組合,非畫板元素是模塊組件
 *    containerOptions: {} 如果我配置了module為true,代表當前是個模塊,模塊身份可以去配置容器物件的屬性
 *    propsValue: {} // 里面包含了組件所有的data物件屬性,它不需要再基本結構中配置,他會在生成組件的時候會放到該配置中來
 */
import {pageWh, defaultStyle, moduleContainer} from './config'

// 容器的基本結構
export const plContainer = {
  elName: 'pl-container',
  title: '自由容器',
  icon: 'iconfont iconrongqi',
  pointList: ['b'], // 模塊拖動的方向有哪些
  // 容器最外層盒子的樣式
  containerStyle: { // 容器大盒子的樣式
    marginBottom: 10
  },
  allowed: true, // 代表我當前容器是個畫板,拖影片板元素可以放到容器上面
  showTitle: true, // 是否顯示頭部
  // 容器頭部的樣式
  titleStyle: {
    height: 50,
    lineHeight: 50
  },
  titleBarName: '標題欄',
  // 容器畫板的默認樣式
  commonStyle: {
    width: pageWh.width,
    height: 250,
    position: 'relative',
    minHeight: 50, // 容器里面的畫板最小高度值
    backgroundColor: '#fff'
  },
  childNode: [] // 容器子節點的集裝箱
}

// 基礎組件
const BasicComponents = [
  {
    title: '基礎組件',
    components: [
      plContainer,
      {
        elName: 'pl-text',
        title: '文本',
        icon: 'iconfont iconwenbenyu',
        pointList: [], // 控制組件拖動的方向
        contenteditable: false,
        placeholder: '點擊輸入內容',
        commonStyle: {
          ...defaultStyle,
          padding: 8,
          fontSize: 15,
          lineHeight: 17,
          height: 'auto',
          textAlign: 'left',
          minWidth: 35,
          width: 160
        }
      },
      {
        elName: 'pl-button',
        title: '按鈕',
        icon: 'iconfont iconanniu',
        pointList: ['lt', 'rt', 'lb', 'rb', 'l', 'r', 't', 'b'], // 控制組件拖動的方向
        contenteditable: false,
        options: {
          classList: [],
          lineHeightChange: true // 表示行高需要隨著拖動的高度變化
        },
        commonStyle: {
          ...defaultStyle,
          fontSize: 15,
          lineHeight: 36,
          height: 36,
          textAlign: 'center',
          minWidth: 35,
          minHeight: 36,
          width: 80
        }
      },
      {
        elName: 'cube-nav',
        title: '魔方導航',
        icon: 'iconfont iconfenlei',
        module: true,
        containerOptions: {
          ...moduleContainer,
          titleBarName: '魔方導航模塊'
        },
        options: {
          classList: []
        }
      },
      {
        elName: 'carousel',
        title: '多圖文輪播',
        icon: 'iconfont iconlunbotu',
        module: true,
        containerOptions: {
          ...moduleContainer,
          titleBarName: '多圖文輪播'
        },
        options: {
          classList: []
        }
      }
    ]
  }
]

const components = [...BasicComponents]

// 遍歷判斷找出畫板元素的組件
// 在拖拽元素到畫板的時候,會判斷當前拖動的組件是否在這里面存在,存在才可以添加組件到畫板容器
// 必須是畫板組件
export const drawingComponent = components.map(item => item.components.map(con => {
  if (!con.module && con.elName !== 'pl-container') return con.elName
}))[0].filter(item => item)

export default components

 

頁面整體結構

 

 

 

 

核心代碼

編輯器核心代碼,基于 Vue 動態組件特性實作:

 

 

 

 

// 獲取需要繪畫的節點資料(整個可視化編輯器的最重要的東西)
export const getNodeElement = (nodeData, type) => {
  // 如果不存在該組件就直接回傳
  if (!nodeData || !componentsName.includes(camelCase(nodeData.elName).toLowerCase())) {
    Message.error({message: '沒有該模塊!', type: 'warning', duration: 2000})
    return null
  }
  //  需要添加的節點元素物件
  let nodeElement
  // 獲取當前組件的data資料(非常重要,它將是你原始組件的初始化資料,你右邊的屬性控制就是去更改的它)
  let props = getComponentProps(nodeData.elName)
  // 獲取需要添加的節點元素的資料結構
  nodeElement = deepClone(getElementConfig({...nodeData, needProps: props}))
  // 注意注意注意: 如果我進來的不是容器,那么就需要包裝一層容器,在回傳節點
  // type如果存在,代表我是往容器里面加節點不需要被容器包裹,就不需要執行if陳述句了
  if (nodeElement.elName !== 'pl-container' && type !== '我是往容器里面加節點不需要被容器包裹') {
    // 獲取pl-container容器組件的data資料
    let props = getComponentProps('pl-container')
    // 獲取容器的基本結構
    let containerNodeData =https://www.cnblogs.com/plBlog/p/ getElementConfig({...plContainer, needProps: props})

    // 什么是非畫板元素,就是不能再自由容器中拖動和自由組合,非畫板元素是模塊組件
    // 下面if陳述句是做非畫板元素的關鍵,意思就是非畫板元素,它也屬于自由容器中,但是它不能拖動
    // 如果當前組件是一個模塊, 就需要執行下面的陳述句
    if (nodeElement.module) {
      // 如果是模塊,那么就去看是否改變了容器的樣式,沒有改變默認給個改變容器的基本值
      let cops = judgeObject(nodeElement.containerOptions) ? nodeElement.containerOptions : moduleContainer
      // 合并容器的屬性(很好理解就是去覆寫掉原來容器的屬性,因為原來容器的屬性是為了畫板而生的,但是模塊本身也是被容器包裹的,所以需要去覆寫容器的配置)
      let newContainer = {...containerNodeData, ...cops}
      // 洗掉當前需要添加的節點,里面的配置容器物件
      delete nodeElement.containerOptions
      // 然后再把需要添加的節點放入容器中
      newContainer.childNode.push(nodeElement)
      return deepClone(newContainer)
    }

    // 把需要添加的元素放入到容器節點中
    containerNodeData.childNode.push(nodeElement)
    // 匯出容器
    return deepClone(containerNodeData)
  }
  // 回傳當前組件
  return nodeElement
}

組件庫

撰寫組件,考慮的是組件庫,所以我們竟可能讓我們的組件支持全域引入和按需引入,如果全域引入,那么所有的組件需要要注冊到Vue component 上,并匯出:

/**
 * 組件庫入口
 * */
// 基礎組件
import plEditDiv from './editDiv' // 必須放第一個位置引入 因為下面的組件有用到它
import plText from './text'
import plButton from './Button'
import plContainer from './container'
import cubeNav from './cubeNav'
import carousel from './carousel'
// 所有組件串列
const components = [
  plEditDiv,
  plText,
  plButton,
  plContainer,
  cubeNav,
  carousel
]

let plRegisterComponentsObject = {}
let componentsName = []

components.forEach(item => {
  plRegisterComponentsObject[item.name] = item
  // 匯出當前組件的組件名
  if (item.name && typeof item.name === 'string') {
    componentsName.push(item.name.toLowerCase())
  }
})

// 定義 install 方法,接收 Vue 作為引數
const install = function (Vue) {
  // 判斷是否安裝,安裝過就不繼續往下執行
  if (install.installed) return
  install.installed = true
  // 遍歷注冊所有組件
  components.map(component => Vue.component(component.name, component))
}

export {
  componentsName,
  plEditDiv,
  cubeNav,
  plButton,
  carousel,
  plText,
  plContainer,
  plRegisterComponentsObject
}

export default {
  install
}

 

啟動運行

npm run dev

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/3984.html

標籤:HTML5

上一篇:HTML5(五)Geolocation

下一篇:瀏覽器跨域問題分析

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more