欧美vvv,亚洲第一成人在线,亚洲成人欧美日韩在线观看,日本猛少妇猛色XXXXX猛叫

新聞資訊

    前言

    我們在書寫echarts組件的時候,往往會發現:

    其打包的chunk包是全量的,比較大。

    調用api麻煩,要注意dom的渲染時機,資源清除,大小自適應。

    不支持css變量,無法動態換膚的。

    本文就圍繞以上幾點進行提供一個解決方案。

    技術棧

    Vue/React Echarts TS 本文以Vue為例,文末會貼上React實現方案

    github倉庫地址: github.com/Freedom-FJ/…

    依賴引入

    首先為了解決echarts的全量引入問題,我們需要單獨書寫一個echarts依賴引入文件。

    • 引入部分的 echarts/componentscharts 等,把我們項目中用到的echarts部分依賴引入進來
    • echarts.use 掛載到我們的echarts實例上。
    • 書寫 echarts.draw 語法糖,可以一鍵對dom節點進行初始化,清除并繪制新的echarts對象。

    utils/echarts/index.ts

    ts復制代碼/*
     * @Author: mjh
     * @Date: 2023-08-11 12:16:33
     * @LastEditors: mjh
     * @LastEditTime: 2023-08-15 10:20:14
     * @Description:
     */
    import * as echarts from 'echarts/core'
    import { GraphicComponent, GridComponent, LegendComponent, PolarComponent, TitleComponent, TooltipComponent } from 'echarts/components'
    import { BarChart, BoxplotChart, LineChart, PieChart, RadarChart } from 'echarts/charts'
    import { UniversalTransition } from 'echarts/features'
    import { CanvasRenderer } from 'echarts/renderers'
    
    echarts.use([
        GraphicComponent,
        GridComponent,
        TooltipComponent,
        LegendComponent,
        TitleComponent,
        LineChart,
        BarChart,
        PieChart,
        BoxplotChart,
        CanvasRenderer,
        UniversalTransition,
        RadarChart,
        PolarComponent,
    ])
    // import * as echarts from 'echarts'
    
    // 初始化語法糖
    const draw=(dom: HTMLElement, option: Record<string, any>)=> {
        const chart=echarts.init(dom)
        chart.clear()
        chart.setOption(option)
        return chart
    }
    
    export default {
        ...echarts,
        draw
    } as any

    接下來我們要書寫一個 echarts 組件,只需要我們傳入option 就可以自動的繪制echarts節點。

    書寫組件

    簡單組件

    我們先寫一個簡單的vue顯示echarts的組件,我們需要在初始化的時候獲取到dom節點,并且監聽option如參,動態的更新渲染echarts實例。 但是不要忘了在組件銷毀的時候清除echarts實例,防止占用內存。

    vue復制代碼<template>
        <div ref="domBox" :style="{ width, height }">
            <div ref="domRef" :style="{ width, height }" />
        </div>
    </template>
    
    <script lang="ts" setup>
    import { watch, ref, onMounted, onUnmounted } from 'vue'
    import type { ECharts } from 'echarts'
    import echarts from './index'
    const props=defineProps({
        width: {
            type: String,
            default: '100%',
        },
        height: {
            type: String,
            default: '100%',
        },
        options: {
            type: Object,
            default: null,
        },
    })
    
    const domRef=ref(null)
    const domBox=ref(null)
    let chartObj: null | ECharts=null
    
    
    onMounted(()=> {
        if (!domRef.value) return
        init()
        if (props.options)
            drawOption()
    })
    
    onUnmounted(()=> {
        if (chartObj) {
            chartObj.dispose()
            chartObj=null
        }
    })
    
    watch(()=> props.options, ()=> drawOption())
    
    // 初始化
    const init=()=> {
        chartObj=(echarts.init(domRef.value) as any)
    }
    
    const drawOption=()=> {
        if(!chartObj) return
        chartObj.setOption(props.options)
    }
    </script>

    監聽dom更新

    不要忘了我們在繪制echarts實例的時候,如果我們已經繪制完了,但是我們的外部盒子尺寸發生變化了怎么辦,這個時候我們就要監聽我們的外部盒子的尺寸變化來動態的 resize 我們的echarts實例。

    MutationObserver 接口提供了監視對 DOM 樹所做更改的能力。創建并返回一個新的 MutationObserver 它會在指定的 DOM 發生變化時被調用。 其的 observe() 方法配置了 MutationObserver 對象的回調方法以開始接收與給定選項匹配的 DOM 變化的通知。 根據配置,觀察者會觀察 DOM 樹中的單個 Node,也可能會觀察被指定節點的部分或者所有的子孫節點。 要停止 MutationObserver(以便不再觸發它的回調方法),需要調用 MutationObserver.disconnect() 方法。

    語法

    js復制代碼var mutationObserver=new MutationObserver(callback);
    mutationObserver.observe(target[, options])
    • callback
    • 一個回調函數,每當被指定的節點或子樹以及配置項有 DOM 變動時會被調用。回調函數擁有兩個參數:一個是描述所有被觸發改動的 MutationRecord 對象數組,另一個是調用該函數的 MutationObserver 對象。

    對于其option下面我們需要用到以下幾個語法

    • subtree 可選
    • 當為 true 時,將會監聽以 target 為根節點的整個子樹。包括子樹中所有節點的屬性,而不僅僅是針對 target。默認值為 false
    • childList 可選
    • 當為 true 時,監聽 target 節點中發生的節點的新增與刪除(同時,如果 subtreetrue,會針對整個子樹生效)。默認值為 false
    • attributes 可選
    • 當為 true 時觀察所有監聽的節點屬性值的變化。默認值為 true,當聲明了 attributeFilterattributeOldValue,默認值則為 false
    • characterData 可選
    • 當為 true 時,監聽聲明的 target 節點上所有字符的變化。默認值為 true,如果聲明了 characterDataOldValue,默認值則為 false

    獲取實例監聽變化

    js復制代碼const observer=new MutationObserver((mutationsList)=> {
        // 循環尋找我們的echarts dom實例,發現更新的內容包含則調用 resize 方法更新echarts的尺寸
        for (const mutation of mutationsList)
            if (mutation.target===echartDomBox.value) chartObj && chartObj.resize()
    })
    
    observer.observe(domBox.value, {
        attributes: true,
        childList: false,
        characterData: true,
        subtree: true
    })

    我們將其應用到組建內,同時為了增加交互的舒適我們還可以利用echarts api 的 showLoading 加上loading的動畫效果,(也可以使用 hideLoading 方法隱藏效果),為我們組件在未渲染實例時提升交互體驗。

    vue復制代碼<template>
        <div ref="domBox" :style="{ width, height }">
            <div ref="domRef" :style="{ width, height }" />
        </div>
    </template>
    
    <script lang="ts" setup>
    import { watch, ref, onMounted, onUnmounted, nextTick } from 'vue'
    import type { ECharts } from 'echarts'
    import echarts from './index'
    
    const props=defineProps({
        width: {
            type: String,
            default: '100%',
        },
        height: {
            type: String,
            default: '100%',
        },
        options: {
            type: Object,
            default: null,
        },
    })
    
    const domRef=ref(null)
    const domBox=ref(null)
    let chartObj: null | ECharts=null
    let observer: null | MutationObserver=null // dom 監聽
    
    onMounted(()=> {
        if (!domRef.value) return
        init()
        !props.options && chartObj.showLoading({
            text: '',
            color: '#409eff',
            textColor: '#000',
            maskColor: 'rgba(255, 255, 255, .95)',
            zlevel: 0,
            lineWidth: 2,
        })
            
        if (props.options)
            drawOption()
    
        observer=new MutationObserver((mutationsList)=> {
            for (const mutation of mutationsList)
                if (mutation.target===domBox.value) chartObj && chartObj.resize()
        })
        // 注意: 要放在nextTick內,,因為初始化時我們已經進行了一次option的更新操作
        nextTick(()=> {
            domBox.value && (observer as MutationObserver).observe(domBox.value, {
                attributes: true,
                childList: false,
                characterData: true,
                subtree: true
            })
        })
    
        setTimeout(()=> {
            chartObj && chartObj.resize()
        }, 1000)
    })
    
    onUnmounted(()=> {
        if (chartObj) {
            chartObj.dispose()
            chartObj=null
        }
        // 注意銷毀監聽器
        observer && observer.disconnect()
    })
    
    watch(()=> props.options, ()=> drawOption())
    
    // 初始化
    const init=()=> {
        chartObj=(echarts.init(domRef.value) as any)
    }
    
    const drawOption=()=> {
        if(!chartObj) return
        chartObj.hideLoading()
        chartObj.setOption(props.options)
    }
    </script>

    皮膚切換

    主題切換現在比較主流的而且適配最好的是CSS變量的方案,也就是所有的顏色都采用css變量進行替換。我們只需要引入新的樣式表,不需要額外的js代碼,瀏覽器會自動更新CSSOM樹,重繪所有dom的顏色,實現主體切換。

    但是echarts不支持皮膚切換,因為其顏色是通過option傳入的,使用canvas渲染實例,而且其傳入顏色變量的字符串是無法識別的,必須直接傳入顏色值,不支持css變量動態渲染,但是我們可以幫他做這個操作。

    我們在echarts 傳入的opiton的所有顏色處,做一層代理,我們傳入所有的顏色都是變量的字符串例如:'var(--dv-color-1)' 在渲染的時候通過中間函數,將所有的顏色變量轉化成真正的顏色值例如: #fff 那么只需要監聽主題的切換然后重新在將傳入的option進行更新顏色值再更新echarts即可。

    好了思路有了,那我們開干!。

    切換顏色值

    首先書寫一個 replaceVarStrings 中間函數用于將option內的所有顏色變量字符串都轉化為真正的顏色值,匹配 var() 字符串并將其中間的顏色提取出來。 當然我們需要用到宏api getComputedStyle 實時動態計算獲取我們的dom實例的樣式屬性,其返回值的 getPropertyValue 方法傳入變量名稱字符串就會返回實時的變量顏色值。

    但是getComputedStyle會導致瀏覽器的重排,都為求一個“即時性”和“準確性”。

    ts復制代碼/**
     * echarts樣式
     */
    export const useThemeValue=(styleVariables: string)=> {
        return getComputedStyle(document.documentElement).getPropertyValue(styleVariables)
    }
    
    export function replaceVarStrings(obj: Record<string, any>) {
        const newObj: Record<string, any>=Array.isArray(obj) ? [] : {}
        for (const key in obj) {
            if (typeof obj[key]==='object') {
                newObj[key]=replaceVarStrings(obj[key]) // 遞歸處理子對象
            }
            else if (typeof obj[key]==='string' && obj[key].startsWith('var(') && obj[key].endsWith(')')) {
                const varContent=obj[key].slice(4, -1) // 提取括號內的內容
                newObj[key]=useThemeValue(varContent) // 替換為括號內的內容
            }
            else {
                newObj[key]=obj[key] // 其他情況直接復制值
            }
        }
    
        return newObj
    }

    監聽主題切換

    以我們的element-plus 組件庫主題切換為例,其切換為 dark 模式是在html 的標簽上增加一個 dark的class類

    html復制代碼<!-- 正常 模式 -->
    <html> </html>
    
    <!-- dark 模式 -->
    <html class="dark"> </html>

    通過一套 dark 的變量樣式覆蓋原來的html下的樣式

    css復制代碼html {
        --dv-color-background-base: #000b1a;
        --dv-color-background-overlay: #000b1a;
        --dv-color-background-page: #000b1a;
    }
    
    /* dark 模式 */
    html .dark {
        --dv-color-background-base: #000b1a;
        --dv-color-background-overlay: #000b1a;
        --dv-color-background-page: #000b1a;
    }

    剩下只需要監聽html dom節點的class列表即可。 但是監聽 div span document這種很常見,可是怎么監聽 html啊? 其實html 標簽也是一個標簽,你可以將它當作一個dom盒子,只需要像獲取div一樣 直接 document.querySelector('html') 即可獲取到他的對象了。 監聽class類還是用上文講到的 MutationObserver ,只是不同的是 observer 方法的入參屬性加上了 attributes, attributeFilter 配置:

    • attributes 可選 當為 true 時觀察所有監聽的節點屬性值的變化。默認值為 true,當聲明了 attributeFilterattributeOldValue,默認值則為 false
    • attributeFilter 可選 一個用于聲明哪些屬性名會被監聽的數組。如果不聲明該屬性,所有屬性的變化都將觸發通知。

    當然監聽dom的方法有很多 對于針對dom大小的監聽也可以使用其他方法來實現,文末的React實現方案就是用 ResizeObserver 來監聽的, 我們vue就以 MutationObserver 為例。

    接下來寫一個簡單的hook,返回一個響應式的isDark變量,用于讓我們的echarts組件監聽即可,在此我定義為 isDark: boolean 的形式,如果涉及到多套主題的話,也可以對下面代碼進行修改,把返回值改成 theme: 'dark', 字符串的形式。

    ts復制代碼/**
     * @name: 判斷當前主題hook
     * @desc:
     * @return {*}
     */
    export const useTheme=()=> {
        const htmlDom=document.querySelector('html')
        if (!htmlDom) return { isDark: ref(false) }
        const isDark=ref(!!htmlDom.classList.contains('dark'))
    
        // 創建 MutationObserver 實例
        const observer=new MutationObserver((mutationsList)=> {
            for (const mutation of mutationsList) {
                if (mutation.type==='attributes' && mutation.attributeName==='class') {
                    const currentClass=(mutation.target as any).className
                    isDark.value=currentClass.includes('dark')
                }
            }
        })
    
        // 配置 MutationObserver 監聽的選項
        const observerOptions={
            attributes: true,
            attributeFilter: ['class'],
        }
    
        // 開始監聽目標節點
        observer.observe(htmlDom, observerOptions)
    
        return {
            isDark
        }
    }

    有了這些前置條件,這不是咱們的組件就呼之欲出了,只需要修改 drawOption 方法進行中間顏色代理,并且增加一個監聽器監聽 isDark 變量即可。

    vue復制代碼<template>
        <div ref="domBox" :style="{ width, height }">
            <div ref="domRef" :style="{ width, height }" />
        </div>
    </template>
    
    <script lang="ts" setup>
    import type { ECharts } from 'echarts'
    import { watch, ref, onMounted, onUnmounted, nextTick } from 'vue'
    import { replaceVarStrings, useTheme } from './utils'
    import echarts from './index'
    const props=defineProps({
        width: {
            type: String,
            default: '100%',
        },
        height: {
            type: String,
            default: '100%',
        },
        options: {
            type: Object,
            default: null,
        },
    })
    const { isDark }=useTheme()
    
    const domRef=ref(null)
    const domBox=ref(null)
    let chartObj: null | ECharts=null
    let observer: null | MutationObserver=null // dom 監聽
    
    onMounted(()=> {
        if (!domRef.value) return
        init()
        drawOption()
        observer=new MutationObserver((mutationsList)=> {
            for (const mutation of mutationsList)
                if (mutation.target===domBox.value) chartObj && chartObj.resize()
        })
    
        nextTick(()=> {
            domBox.value && (observer as MutationObserver).observe(domBox.value, {
                attributes: true,
                childList: false,
                characterData: true,
                subtree: true
            })
        })
    
        setTimeout(()=> {
            chartObj && chartObj.resize()
        }, 1000)
    })
    
    onUnmounted(()=> {
        if (chartObj) {
            chartObj.dispose()
            chartObj=null
        }
        observer && observer.disconnect()
    })
    
    watch(()=> props.options, ()=> drawOption())
    watch(()=> isDark.value, ()=> drawOption())
    
    // 初始化
    const init=()=> {
        chartObj=(echarts.init(domRef.value) as any)
    }
    
    const drawOption=()=> {
        if(!chartObj) return
        if(!props.options) {
            chartObj.clear()
            chartObj.showLoading({
                text: '',
                color: '#409eff',
                textColor: '#000',
                maskColor: 'rgba(255, 255, 255, .95)',
                zlevel: 0,
                lineWidth: 2,
            })
        }
        else {
            chartObj.hideLoading()
            chartObj.setOption(replaceVarStrings(props.options))
        }
    }
    </script>

    lazy模式

    為了提高組建的擴展性,我們也可以為組建增加一個非自動化的lazy模式。當我們開啟了lazy模式以后,組建將不會自動的收集option依賴更新dom,也不用傳入響應式的option,而是需要外部調用組建expose的方法控制更新時機。

    vue復制代碼<template>
        <div ref="domBox" :style="{ width, height }">
            <div ref="domRef" :style="{ width, height }" />
        </div>
    </template>
    
    <script lang="ts" setup>
    import { watch, ref, onMounted, onUnmounted, nextTick } from 'vue'
    import { replaceVarStrings, useTheme } from './utils'
    import type { ECharts } from 'echarts'
    import echarts from './index'
    const props=defineProps({
        width: {
            type: String,
            default: '100%',
        },
        height: {
            type: String,
            default: '100%',
        },
        lazy: {
            type: Boolean,
            default: false,
        },
        options: {
            type: Object,
            default: null,
        },
    })
    const { isDark }=useTheme()
    
    const domRef=ref(null)
    const domBox=ref(null)
    let chartObj: null | ECharts=null
    let observer: null | MutationObserver=null // dom 監聽
    
    onMounted(()=> {
        if (!domRef.value) return
        init()
        if(props.lazy) return
        drawOption()
        observer=new MutationObserver((mutationsList)=> {
            for (const mutation of mutationsList)
                if (mutation.target===domBox.value) resize()
        })
        nextTick(()=> {
            domBox.value && (observer as MutationObserver).observe(domBox.value, {
                attributes: true,
                childList: false,
                characterData: true,
                subtree: true
            })
        })
    })
    
    onUnmounted(()=> {
        if (chartObj) {
            chartObj.dispose()
            chartObj=null
        }
        observer?.disconnect()
    })
    
    watch(()=> props.options, ()=> !props.lazy && drawOption())
    watch(()=> isDark.value, ()=> !props.lazy && drawOption())
    
    // 繪制方法
    const drawOption=(options=props.options)=> {
        if(!chartObj) return
        if(!options) {
            chartObj.clear()
            chartObj.showLoading({
                text: '',
                color: '#409eff',
                textColor: '#000',
                maskColor: 'rgba(255, 255, 255, .95)',
                zlevel: 0,
                lineWidth: 2,
            })
        }
        else {
            chartObj.hideLoading()
            chartObj.setOption(replaceVarStrings(options))
        }
    }
    
    // 初始化
    const init=()=> {
        chartObj=(echarts.init(domRef.value) as any)
    }
    
    // 重繪 自適應尺寸
    const resize=()=> {
        chartObj?.resize()
    }
    
    defineExpose({
        drawOption,
        resize,
        init
    })
    </script>

    使用

    vue復制代碼<template>
        <div style="height: 300px;width: 200px;">
            <BaseECharts :options="options" />
        </div>
    </template>
    <script setup lang="ts">
    import BaseECharts from '@/utils/echarts/BaseECharts.vue'
    const options=ref<any>({
            xAxis: {
              type: 'category',
              data: [],
            },
            color: ['var(--dv-color-danger)'], // 顏色的變量字符串
            yAxis: {
              type: 'value',
            },
            series: [
              {
                data: [],
                type: 'line',
              },
            ],
      })
    
    setTimeout(()=> {
      options.value={
            xAxis: {
              type: 'category',
              data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
            },
            color: ['var(--dv-color-danger)'], // 顏色的變量字符串
            yAxis: {
              type: 'value',
            },
            series: [
              {
                data: [150, 230, 224, 218, 135, 147, 260],
                type: 'line',
              },
            ],
        }
    }, 3000)
    </script>

    react

    對于react方案,我們在實現useTheme 上會有一些不一樣,我們的變量需要用useState實現,注意 useState 避免放在條件return語句之后。 useTheme:

    ts復制代碼/**
     * @name: 判斷當前主題hook
     * @desc:
     * @return {*}
     */
    export const useTheme=()=> {
        const htmlDom=document.querySelector('html');
        const [isDark, setIsDark]=useState(!!htmlDom?.classList.contains('dark'));
        const observer=useRef<MutationObserver>();
        if (!htmlDom) return { isDark: false };
        // 創建 MutationObserver 實例
        if (!observer.current) {
            observer.current=new MutationObserver((mutationsList)=> {
                for (const mutation of mutationsList) {
                    if (mutation.type==='attributes' && mutation.attributeName==='class') {
                        const currentClass=(mutation.target as HTMLElement).className;
                        setIsDark(currentClass.includes('dark'));
                    }
                }
            });
        }
    
        // 配置 MutationObserver 監聽的選項
        const observerOptions={
            attributes: true,
            attributeFilter: ['class'],
        };
    
        // 開始監聽目標節點
        observer.current.observe(htmlDom, observerOptions);
    
        return {
            isDark,
        };
    };

    在組件實現這邊我們換用更為簡單的api ResizeObserver 接口可以監視 Element 內容盒或邊框盒或者 SVGElement 邊界尺寸的變化。

    注意記得在 useEffect 的 return 語句中將我們的 ResizeObserver 監聽器和實例銷毀。

    組件:

    tsx復制代碼/*
     * @Author: mjh
     * @Date: 2023-11-25 16:04:13
     * @LastEditors: mjh
     * @LastEditTime: 2023-11-25 23:23:28
     * @Description:
     */
    import { useEffect, useRef } from 'react';
    import echarts from './index';
    import { replaceVarStrings, useTheme } from './utils';
    export interface EchartControllerProps {
        width?: string;
        height?: string;
        options?: Record<string, any> | null;
    }
    export default function EchartController(props: EchartControllerProps) {
        const { height='100%', width='100%', options }=props;
        const chartRef=useRef<any>();
        const cInstance=useRef<any>();
        const { isDark }=useTheme();
    
        useEffect(()=> {
            if (!chartRef.current) return;
            if (!cInstance.current) {
                cInstance.current=echarts.init(chartRef.current);
            }
            const observer=new ResizeObserver(()=> {
                cInstance.current.resize();
            });
            observer.observe(chartRef.current);
            return ()=> {
                cInstance.current?.dispose()
                observer.disconnect();
            };
        }, []);
    
        useEffect(()=> {
            if (!cInstance.current) return;
            if (!options) {
                cInstance.current.showLoading({
                    text: '',
                    color: '#409eff',
                    textColor: '#000',
                    maskColor: 'rgba(255, 255, 255, .95)',
                    zlevel: 0,
                    lineWidth: 2,
                });
                return;
            }
            cInstance.current.hideLoading();
            cInstance.current.setOption(replaceVarStrings(options));
        }, [options, isDark]);
    
        return (
          <div ref={chartRef} style={{ height, width }} />
        );
    }

    交互事件

    • 我的大部分項目都不會涉及到echarts交互事件,所以暫時沒有封裝。
    • 如果出現需求,可以簡單做一個組件內部的事件代理即可,也可以根據需求做一些簡單的語法彈,但是還是以場景和需求為準。

    參考資料

    developer.mozilla.org/zh-CN/docs/… developer.mozilla.org/zh-CN/docs/…

    作者:Freedom風間 鏈接:https://juejin.cn/post/7304959484828844043 來源:稀土掘金 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

    系統封裝工具哪個好


    說一說有哪些系統封裝工具哪個好

    最近在網上搜索系統封裝相關問題時,總是會遇到這樣的問題:系統封裝工具哪個好?我不明白究竟是

    哪些人這么無聊。一個左手、一個右手、你來和我說說哪個好?想不通是人為的刷詞、還是真的有人提


    先說下系統封裝工具有哪些

    Nowprep 系統封裝工具(信念論壇-小兵老師的系統封裝工具)

    JM一鍵封裝工具(IT姊妹論壇-非常經典的一款軟件)

    一鍵系統封裝工具(雨林木風論壇-名氣足夠大了吧)


    ES系統封裝工具(IT天空系統封裝工具、老牌系統封裝技術論壇)

    SC系統封裝工具(系統總裁論壇,后起之秀。)


    如果一一舉例,我估計沒有100也有80個,后面我就不進行詳細的舉例了。


    下面回到主題:系統封裝工具哪個好?

    不得不說的一個話題是系統封裝工具的目的是什么?將系統制作成可以重復安裝的一個狀態。

    系統封裝工具的核心是什么?sysprep.exe(微軟官方提供的封裝平臺)

    系統封裝工具的作用是什么?在sysprep.exe的基礎上調用接口(部署前、部署中、部署后...)


    那么,以上我舉例的這些系統封裝工具哪一個做不到這3點?所以系統封裝工具在我看來沒有好壞之分,

    適合自己的才是最好的系統封裝工具。

網站首頁   |    關于我們   |    公司新聞   |    產品方案   |    用戶案例   |    售后服務   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權所有