Преглед изворни кода

refactor(架构调整): iframe demo 引入repl直接显示

gemercheung пре 2 година
родитељ
комит
3d55eb6df8
28 измењених фајлова са 522 додато и 67 уклоњено
  1. 23 23
      docs/.vitepress/config/head.ts
  2. 1 1
      docs/.vitepress/i18n/component/footer.json
  3. 62 0
      docs/.vitepress/vitepress/components/demo/dept.js
  4. 9 0
      docs/.vitepress/vitepress/components/demo/main.vue
  5. 72 3
      docs/.vitepress/vitepress/components/demo/vp-example.vue
  6. 49 0
      docs/.vitepress/vitepress/components/demo/vp-frameRender.ts
  7. 2 2
      docs/.vitepress/vitepress/components/doc-content/vp-page-footer.vue
  8. 5 5
      docs/.vitepress/vitepress/components/doc-content/vp-table-of-content.vue
  9. 4 3
      docs/.vitepress/vitepress/components/vp-demo.vue
  10. 32 0
      docs/.vitepress/vitepress/composables/dept.ts
  11. 1 0
      docs/components.d.ts
  12. 1 2
      docs/examples/audio/basic.vue
  13. 29 12
      docs/examples/tag/basic.vue
  14. 67 0
      docs/examples/tag/custom.vue
  15. 1 0
      docs/package.json
  16. 12 12
      docs/vite.config.ts
  17. 10 2
      docs/zh-CN/component/tag.md
  18. 25 0
      packages/components/basic/audio/__tests__/audio.test.tsx
  19. 8 0
      packages/components/basic/audio/index.ts
  20. 11 0
      packages/components/basic/audio/src/audio.ts
  21. 56 0
      packages/components/basic/audio/src/audio.vue
  22. 2 0
      packages/components/basic/audio/style/css.ts
  23. 2 0
      packages/components/basic/audio/style/index.ts
  24. 1 0
      packages/components/basic/index.ts
  25. 4 1
      packages/kankan-components/component.ts
  26. 21 0
      packages/theme-chalk/src/audio.scss
  27. 1 0
      packages/theme-chalk/src/index.scss
  28. 11 1
      pnpm-lock.yaml

+ 23 - 23
docs/.vitepress/config/head.ts

@@ -6,30 +6,30 @@ import { languages } from '../utils/lang'
 import type { HeadConfig } from 'vitepress'
 
 export const head: HeadConfig[] = [
-    [
-        'link',
-        {
-            rel: 'stylesheet',
-            href: '//at.alicdn.com/t/c/font_2596172_5i5zp5tvfj9.css',
-        },
-    ],
-    [
-        'script',
-        { id: 'scriptImporter' },
-        `
-            (function() { 
-            var script = document.createElement("script"); 
-            script.src="//4dkk.4dage.com/v4-test/www/sdk/kankan-sdk-deps.js?v=4.6.0-alpha.10";
-            setTimeout(() => document.body.append(script))
+    // [
+    //     'link',
+    //     {
+    //         rel: 'stylesheet',
+    //         href: '//at.alicdn.com/t/c/font_2596172_5i5zp5tvfj9.css',
+    //     },
+    // ],
+    // [
+    //     'script',
+    //     { id: 'scriptImporter' },
+    //     `
+    //         (function() {
+    //         var script = document.createElement("script");
+    //         script.src="//4dkk.4dage.com/v4-test/www/sdk/kankan-sdk-deps.js?v=4.6.0-alpha.10";
+    //         setTimeout(() => document.body.append(script))
 
-            script.onload = function(){
-                var script2 = document.createElement("script"); 
-                script2.src = "//4dkk.4dage.com/v4-test/www/sdk/kankan-sdk.js?v=4.6.0-alpha.10";
-                setTimeout(() => document.body.append(script2),100)
-            }
-            })(); 
-    `,
-    ],
+    //         script.onload = function(){
+    //             var script2 = document.createElement("script");
+    //             script2.src = "//4dkk.4dage.com/v4-test/www/sdk/kankan-sdk.js?v=4.6.0-alpha.10";
+    //             setTimeout(() => document.body.append(script2),100)
+    //         }
+    //         })();
+    // `,
+    // ],
     [
         'link',
         {

+ 1 - 1
docs/.vitepress/i18n/component/footer.json

@@ -1,6 +1,6 @@
 {
     "zh-CN": {
-        "source": "Source",
+        "source": "资源",
         "contributors": "Contributors",
         "component": "Component",
         "docs": "Docs"

+ 62 - 0
docs/.vitepress/vitepress/components/demo/dept.js

@@ -0,0 +1,62 @@
+// import { getCurrentInstance } from 'vue'
+
+let installed = false
+await loadStyle()
+await loadKanKanThemeChalkStyle()
+await loadSDKDep()
+await loadSDK()
+
+export function setupDept() {
+    if (installed) return
+    // const instance = getCurrentInstance()
+
+    // const __sdk = (window.__sdk = new window.KanKan({
+    //   num: 'KJ-t-wOXfx2SDFy',
+    //   // server: 'https://test.4dkankan.com',
+    //   server: '/demoServer',
+    // }))
+    // instance.appContext.app.provide('__sdk', __sdk)
+    // instance.appContext.app.use(ElementPlus)
+    installed = true
+}
+
+export function loadStyle() {
+    return new Promise((resolve, reject) => {
+        const link = document.createElement('link')
+        link.rel = 'stylesheet'
+        link.href = '#STYLE#'
+        link.addEventListener('load', resolve)
+        link.addEventListener('error', reject)
+        document.body.append(link)
+    })
+}
+export function loadSDKDep() {
+    return new Promise((resolve, reject) => {
+        const script = document.createElement('script')
+        script.src = '//4dkk.4dage.com/v4-test/www/sdk/kankan-sdk-deps.js?v=4.6.0-alpha.10'
+        script.addEventListener('load', resolve)
+        script.addEventListener('error', reject)
+        document.body.append(script)
+    })
+}
+
+export function loadSDK() {
+    return new Promise((resolve, reject) => {
+        const script = document.createElement('script')
+        script.src = '//4dkk.4dage.com/v4-test/www/sdk/kankan-sdk.js?v=4.6.0-alpha.10'
+        script.addEventListener('load', resolve)
+        script.addEventListener('error', reject)
+        document.body.append(script)
+    })
+}
+
+export function loadKanKanThemeChalkStyle() {
+    return new Promise((resolve, reject) => {
+        const link = document.createElement('link')
+        link.rel = 'stylesheet'
+        link.href = 'https://4dkk.4dage.com/npm_test/kankan-components/theme-chalk/index.css'
+        link.addEventListener('load', resolve)
+        link.addEventListener('error', reject)
+        document.body.append(link)
+    })
+}

+ 9 - 0
docs/.vitepress/vitepress/components/demo/main.vue

@@ -0,0 +1,9 @@
+<script setup>
+import App from './App.vue'
+import { setupDept } from './dept.js'
+setupDept()
+</script>
+
+<template>
+    <App />
+</template>

+ 72 - 3
docs/.vitepress/vitepress/components/demo/vp-example.vue

@@ -1,20 +1,75 @@
 <script setup lang="ts">
-defineProps({
+import { computed, unref } from 'vue'
+import { File, Repl, ReplStore } from '@vue/repl'
+import mainCode from './main.vue?raw'
+import deptCode from './dept.js?raw'
+import type { SFCOptions } from '@vue/repl'
+
+const sfcOptions: SFCOptions = {
+    script: {
+        reactivityTransform: true,
+    },
+}
+
+const props = defineProps({
     file: {
         type: String,
         required: true,
     },
+    raw: {
+        type: String,
+        required: true,
+    },
     demo: {
         type: Object,
         required: true,
     },
 })
+
+const loadSingleData = computed(() => {
+    const store = {
+        'App.vue': decodeURIComponent(props.raw),
+        // files: {
+        //     'con'
+        // },
+    }
+    return window.btoa(JSON.stringify(store))
+})
+const store = new ReplStore({
+    // initialize repl with previously serialized state
+    serializedState: unref(loadSingleData),
+
+    // starts on the output pane (mobile only) if the URL has a showOutput query
+    showOutput: true,
+    // starts on a different tab on the output pane if the URL has a outputMode query
+    // and default to the "preview" tab
+    outputMode: 'preview',
+
+    // specify the default URL to import Vue runtime from in the sandbox
+    // default is the CDN link from unpkg.com with version matching Vue's version
+    // from peerDependency
+    defaultVueRuntimeURL: 'https://cdn.jsdelivr.net/npm/@vue/runtime-dom@latest/dist/runtime-dom.esm-browser.js',
+})
+store.init()
+store.setImportMap({
+    imports: {
+        vue: 'https://cdn.jsdelivr.net/npm/@vue/runtime-dom@latest/dist/runtime-dom.esm-browser.js',
+        '@vue/shared': 'https://cdn.jsdelivr.net/npm/@vue/shared@latest/dist/shared.esm-bundler.js',
+        'kankan-components': 'https://4dkk.4dage.com/npm_test/kankan-components/dist/index.full.min.mjs',
+    },
+})
+const PlaygroundMain = new File('PlaygroundMain.vue', mainCode)
+const deptFile = new File('dept.js', deptCode)
+store.addFile(PlaygroundMain)
+store.addFile(deptFile)
+store.state.mainFile = 'PlaygroundMain.vue'
 </script>
 
 <template>
-    <div class="example-showcase">
+    <div class="example-showcase" antialiased>
         <ClientOnly>
-            <component :is="demo" v-if="demo" v-bind="$attrs" />
+            <!-- <component :is="demo" v-if="demo" v-bind="$attrs" /> -->
+            <Repl :store="store" :sfc-options="sfcOptions" :clear-console="false" :show-compile-output="false" auto-resize />
         </ClientOnly>
     </div>
 </template>
@@ -26,3 +81,17 @@ defineProps({
     background-color: var(--bg-color);
 }
 </style>
+<style lang="scss">
+.vue-repl {
+    height: 600px;
+    .left {
+        display: none;
+    }
+    .right {
+        width: 100% !important;
+    }
+    .tab-buttons {
+        display: none;
+    }
+}
+</style>

+ 49 - 0
docs/.vitepress/vitepress/components/demo/vp-frameRender.ts

@@ -0,0 +1,49 @@
+//@ts-nocheck
+import { createApp, defineComponent, h, onBeforeUpdate, onMounted, ref } from 'vue'
+
+// eslint-disable-next-line vue/one-component-per-file
+export default defineComponent({
+    name: 'RenderToIFrame',
+    props: {
+        css: {
+            type: String,
+            default: '',
+        },
+    },
+    setup(props, { slots }) {
+        const iframeRef = ref<HTMLElement | null>(null)
+        const iframeBody = ref<HTMLElement | null>(null)
+        const iframeHead = ref(null)
+        const iframeStyle = ref(null)
+        let iframeApp = null
+
+        onMounted(() => {
+            iframeBody.value = iframeRef.value.contentDocument.body
+            iframeHead.value = iframeRef.value.contentDocument.head
+            const el = document.createElement('div')
+            iframeRef.value?.classList.add('playground-iframe')
+            el.id = 'app'
+            iframeBody.value.appendChild(el)
+            iframeStyle.value = document.createElement('style')
+            iframeStyle.value.innerHTML = props.css
+            iframeHead.value.appendChild(iframeStyle.value)
+
+            // eslint-disable-next-line vue/one-component-per-file
+            iframeApp = createApp({
+                name: 'IframeRender',
+                setup() {
+                    return () => slots.default()
+                },
+            }).mount(el)
+        })
+        onBeforeUpdate(() => {
+            if (!iframeApp || !iframeRef.value) {
+                return
+            }
+            if (props.css) {
+                iframeStyle.value.innerHTML = props.css
+            }
+        })
+        return () => h('iframe', { ref: iframeRef })
+    },
+})

+ 2 - 2
docs/.vitepress/vitepress/components/doc-content/vp-page-footer.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
-import VPEditLink from './vp-edit-link.vue'
+// import VPEditLink from './vp-edit-link.vue'
 </script>
 
 <template>
     <footer class="page-footer">
         <div class="edit">
-            <VPEditLink />
+            <!-- <VPEditLink /> -->
         </div>
     </footer>
 </template>

+ 5 - 5
docs/.vitepress/vitepress/components/doc-content/vp-table-of-content.vue

@@ -3,11 +3,11 @@ import { computed, ref } from 'vue'
 import { useToc } from '../../composables/use-toc'
 import { useActiveSidebarLinks } from '../../composables/active-bar'
 
-import sponsorLocale from '../../../i18n/component/sponsor.json'
+// import sponsorLocale from '../../../i18n/component/sponsor.json'
 import { useLang } from '../../composables/lang'
-import SponsorsButton from '../sponsors/sponsors-button.vue'
-import SponsorRightTextList from '../sponsors/right-richtext-list.vue'
-import SponsorRightLogoSmallList from '../sponsors/right-logo-small-list.vue'
+// import SponsorsButton from '../sponsors/sponsors-button.vue'
+// import SponsorRightTextList from '../sponsors/right-richtext-list.vue'
+// import SponsorRightLogoSmallList from '../sponsors/right-logo-small-list.vue'
 // import SponsorLarge from '../vp-sponsor-large.vue'
 
 const headers = useToc()
@@ -15,7 +15,7 @@ const marker = ref()
 const container = ref()
 useActiveSidebarLinks(container, marker)
 const lang = useLang()
-const sponsor = computed(() => sponsorLocale[lang.value])
+// const sponsor = computed(() => sponsorLocale[lang.value])
 </script>
 
 <template>

+ 4 - 3
docs/.vitepress/vitepress/components/vp-demo.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { computed, getCurrentInstance, toRef } from 'vue'
+import { computed, getCurrentInstance, ref, toRef } from 'vue'
 import { isClient, useClipboard, useToggle } from '@vueuse/core'
 import { CaretTop } from '@element-plus/icons-vue'
 import { useLang } from '../composables/lang'
@@ -10,6 +10,8 @@ import demoBlockLocale from '../../i18n/component/demo-block.json'
 
 import Example from './demo/vp-example.vue'
 import SourceCode from './demo/vp-source-code.vue'
+import '@vue/repl/style.css'
+import '../composables/dept'
 
 const props = defineProps<{
     demos: object
@@ -69,8 +71,7 @@ const copyCode = async () => {
         <p text="sm" v-html="decodedDescription" />
 
         <div class="example">
-            <Example :file="path" :demo="formatPathDemos[path]" />
-
+            <Example :file="path" :demo="formatPathDemos[path]" :raw="rawSource" />
             <ElDivider class="m-0" />
 
             <div class="op-btns">

+ 32 - 0
docs/.vitepress/vitepress/composables/dept.ts

@@ -0,0 +1,32 @@
+await loadSDKDep()
+await loadSDK()
+await loadKanKanThemeChalkStyle()
+export function loadSDKDep() {
+    return new Promise((resolve, reject) => {
+        const script = document.createElement('script')
+        script.src = '//4dkk.4dage.com/v4-test/www/sdk/kankan-sdk-deps.js?v=4.6.0-alpha.10'
+        script.addEventListener('load', resolve)
+        script.addEventListener('error', reject)
+        document.body.append(script)
+    })
+}
+
+export function loadSDK() {
+    return new Promise((resolve, reject) => {
+        const script = document.createElement('script')
+        script.src = '//4dkk.4dage.com/v4-test/www/sdk/kankan-sdk.js?v=4.6.0-alpha.10'
+        script.addEventListener('load', resolve)
+        script.addEventListener('error', reject)
+        document.body.append(script)
+    })
+}
+export function loadKanKanThemeChalkStyle() {
+    return new Promise((resolve, reject) => {
+        const link = document.createElement('link')
+        link.rel = 'stylesheet'
+        link.href = 'https://4dkk.4dage.com/npm_test/kankan-components/theme-chalk/index.css'
+        link.addEventListener('load', resolve)
+        link.addEventListener('error', reject)
+        document.body.append(link)
+    })
+}

+ 1 - 0
docs/components.d.ts

@@ -37,6 +37,7 @@ declare module '@vue/runtime-core' {
         LeftBottomLayerSvg: typeof import('./.vitepress/vitepress/components/home/svg/left-bottom-layer-svg.vue')['default']
         LeftLayerSvg: typeof import('./.vitepress/vitepress/components/home/svg/left-layer-svg.vue')['default']
         Light: typeof import('./.vitepress/vitepress/components/icons/light.vue')['default']
+        Main: typeof import('./.vitepress/vitepress/components/demo/main.vue')['default']
         MainColor: typeof import('./.vitepress/vitepress/components/globals/main-color.vue')['default']
         NeutralColor: typeof import('./.vitepress/vitepress/components/globals/neutral-color.vue')['default']
         ParallaxHome: typeof import('./.vitepress/vitepress/components/globals/parallax-home.vue')['default']

+ 1 - 2
docs/examples/audio/basic.vue

@@ -5,6 +5,5 @@
 </template>
 
 <script lang="ts" setup>
-import { UIAudio } from '@kankan-components/components'
-import '@kankan-components/theme-chalk/dist/index.css'
+import { UIAudio } from 'kankan-components'
 </script>

+ 29 - 12
docs/examples/tag/basic.vue

@@ -1,19 +1,31 @@
 <script setup lang="ts">
-import { onMounted, provide, ref } from 'vue'
+import { onMounted, onUnmounted, provide, ref, unref } from 'vue'
 import { UITag } from 'kankan-components'
 
 const tags = ref<any>([])
 
 onMounted(() => {
+    console.warn('onMounted')
     const __win = window as any
-    const __sdk = (__win.__sdk = new __win.KanKan({
-        num: 'KJ-t-wOXfx2SDFy',
-        // server: 'https://test.4dkankan.com',
-        server: '/demoServer',
-    }))
-    provide('__sdk', __sdk)
-    __sdk.TagManager.on('loaded', (data: any) => __sdk.TagManager.load((tags.value = data) && tags.value))
-    __sdk.mount('#scene').render()
+    if (!__win.__sdk) {
+        const __sdk = (__win.__sdk = new __win.KanKan({
+            num: 'KJ-t-wOXfx2SDFy',
+            // server: 'https://test.4dkankan.com',
+            server: '/demoServer',
+        }))
+        provide('__sdk', __sdk)
+
+        __sdk.TagManager.on('loaded', (data: any) => __sdk.TagManager.load((tags.value = data) && tags.value))
+        __sdk.mount('#scene').render()
+    }
+})
+onUnmounted(() => {
+    const __win = window as any
+    console.warn('onUnmounted')
+    if (__win.__sdk) {
+        __win.__sdk = null
+        location.reload()
+    }
 })
 const handleTagview = ({ id }) => {
     console.log('id', id)
@@ -21,7 +33,7 @@ const handleTagview = ({ id }) => {
 </script>
 
 <template>
-    <div id="scene">
+    <div id="scene" class="scene">
         <Teleport v-if="tags.length > 0" to=".kankan-plugins">
             <div xui_tags_view>
                 <UITag v-for="(item, index) in tags" :key="index" :tag="item" @click="handleTagview" />
@@ -43,8 +55,13 @@ const handleTagview = ({ id }) => {
 <style>
 html,
 body,
-#app,
-#scene {
+#app {
+    width: 100%;
+    height: 100%;
+    padding: 0;
+    margin: 0;
+}
+.scene {
     width: 100%;
     height: 600px;
     padding: 0;

+ 67 - 0
docs/examples/tag/custom.vue

@@ -0,0 +1,67 @@
+<script setup lang="ts">
+import { onMounted, onUnmounted, provide, ref, unref } from 'vue'
+import { UITag } from 'kankan-components'
+
+const tags = ref<any>([])
+
+onMounted(() => {
+    console.warn('onMounted')
+    const __win = window as any
+    if (!__win.__sdk) {
+        const __sdk = (__win.__sdk = new __win.KanKan({
+            num: 'KJ-t-wOXfx2SDFy',
+            // server: 'https://test.4dkankan.com',
+            server: '/demoServer',
+        }))
+        provide('__sdk', __sdk)
+        __sdk.TagManager.on('loaded', (data: any) => __sdk.TagManager.load((tags.value = data) && tags.value))
+        __sdk.mount(`#scene1`).render()
+    }
+})
+onUnmounted(() => {
+    const __win = window as any
+    console.warn('onUnmounted')
+    if (__win.__sdk) {
+        __win.__sdk = null
+        location.reload()
+    }
+})
+const handleTagview = ({ id }) => {
+    console.log('id', id)
+}
+</script>
+
+<template>
+    <div id="scene1" class="scene">
+        <Teleport v-if="tags.length > 0" to=".kankan-plugins">
+            <div xui_tags_view>
+                <UITag v-for="(item, index) in tags" :key="index" :tag="item">
+                    <template #content="{ data, id, isClick, isShow, x, y }">
+                        <div>id:{{ id }}</div>
+                        <div>content:{{ data.content }}</div>
+                        <div>isClick:{{ isClick }}</div>
+                        <div>isShow:{{ isShow }}</div>
+                        <div>x:{{ x }}</div>
+                        <div>y:{{ y }}</div>
+                    </template>
+                </UITag>
+            </div>
+        </Teleport>
+    </div>
+</template>
+<style>
+html,
+body,
+#app {
+    width: 100%;
+    height: 100%;
+    padding: 0;
+    margin: 0;
+}
+.scene {
+    width: 100%;
+    height: 600px;
+    padding: 0;
+    margin: 0;
+}
+</style>

+ 1 - 0
docs/package.json

@@ -13,6 +13,7 @@
         "@element-plus/icons-vue": "^2.0.9",
         "@kankan-components/metadata": "workspace:*",
         "@kankan-components/utils": "workspace:*",
+        "@vue/repl": "^1.3.2",
         "@vue/shared": "^3.2.37",
         "@vueuse/core": "^9.1.0",
         "axios": "^0.27.2",

+ 12 - 12
docs/vite.config.ts

@@ -20,18 +20,18 @@ const alias: Alias[] = [
         replacement: `${path.resolve(__dirname, './.vitepress/vitepress')}/`,
     },
 ]
-// if (process.env.DOC_ENV !== 'production') {
-//     alias.push(
-//         {
-//             find: /^element-plus(\/(es|lib))?$/,
-//             replacement: path.resolve(projRoot, 'packages/element-plus/index.ts'),
-//         },
-//         {
-//             find: /^element-plus\/(es|lib)\/(.*)$/,
-//             replacement: `${path.resolve(projRoot, 'packages')}/$2`,
-//         }
-//     )
-// }
+if (process.env.DOC_ENV !== 'production') {
+    alias.push(
+        {
+            find: /^kankan-components(\/(es|lib))?$/,
+            replacement: path.resolve(projRoot, 'packages/kankan-components/index.ts'),
+        },
+        {
+            find: /^kankan-components\/(es|lib)\/(.*)$/,
+            replacement: `${path.resolve(projRoot, 'packages')}/$2`,
+        }
+    )
+}
 
 export default defineConfig(async ({ mode }) => {
     const env = loadEnv(mode, process.cwd(), '')

+ 10 - 2
docs/zh-CN/component/tag.md

@@ -3,9 +3,9 @@ title: Tag热点
 lang: zh-CN
 ---
 
-# Tags 编辑器
+# Tags 热点
 
-Tags 编辑器使用如下。
+Tags 热点使用如下。
 
 ## 基本用法
 
@@ -14,3 +14,11 @@ Tags 编辑器使用如下。
 tag/basic
 
 :::
+
+## 定制 Slot
+
+:::demo
+
+tag/custom
+
+:::

+ 25 - 0
packages/components/basic/audio/__tests__/audio.test.tsx

@@ -0,0 +1,25 @@
+import { ref } from 'vue'
+import { mount } from '@vue/test-utils'
+import { describe, expect, test } from 'vitest'
+import Audio from '../src/audio.vue'
+// import type { VNode } from 'vue';
+
+// const _mount = (render: () => VNode) => {
+//     return mount(render, { attachTo: document.body })
+//   }
+
+describe('Audio.vue', () => {
+    //   test('render test', async () => {
+    //     const AudioSrc = '';
+    //     const wrapper = mount(() => <Audio src={AudioSrc}></Audio>);
+    //     await nextTick();
+    //   });
+
+    test('play', async () => {
+        const radio = ref('')
+        const wrapper = mount(() => <Audio v-model={radio.value} />)
+        await wrapper.trigger('click')
+        expect(radio.value).toBe('')
+        expect(wrapper.classes()).toContain('is-disabled')
+    })
+})

+ 8 - 0
packages/components/basic/audio/index.ts

@@ -0,0 +1,8 @@
+import { withInstall } from '@kankan-components/utils'
+import Audio from './src/audio.vue'
+
+export const UIAudio = withInstall(Audio)
+
+export default UIAudio
+
+export * from './src/audio'

+ 11 - 0
packages/components/basic/audio/src/audio.ts

@@ -0,0 +1,11 @@
+import { buildProps } from '@kankan-components/utils'
+import type { ExtractPropTypes } from 'vue'
+import type Audio from './audio.vue'
+
+export const audioProps = buildProps({
+    src: { type: String },
+})
+
+export type AudioProps = ExtractPropTypes<typeof audioProps>
+
+export type AudioInstance = InstanceType<typeof Audio>

+ 56 - 0
packages/components/basic/audio/src/audio.vue

@@ -0,0 +1,56 @@
+<template>
+    <div class="ui-audio" @click="clickHandler">
+        <audio ref="audio" autoplay loop @play="rotation">
+            <source :src="src" />
+        </audio>
+        <span v-for="random in randoms" :key="random" :style="{ '--percent': random }" />
+    </div>
+</template>
+
+<script lang="ts" setup>
+import { defineProps, ref, watchEffect } from 'vue'
+import { audioProps } from './audio'
+
+defineOptions({
+    name: 'UIAudio',
+})
+defineProps(audioProps)
+
+const audio = ref()
+const randoms = ref([1, 0.5, 1, 0.5])
+const playIng = ref(false)
+
+let audioTimeout: ReturnType<typeof setTimeout> | undefined
+const rotation = () => {
+    if (!playIng.value) return
+    for (let i = 0; i < randoms.value.length; i++) {
+        randoms.value[i] = Math.random()
+    }
+    audioTimeout = setTimeout(rotation, 200)
+}
+
+watchEffect(() => {
+    if (audio.value) {
+        if (playIng.value) {
+            audio.value.play()
+        } else {
+            audio.value.pause()
+        }
+        audioTimeout && clearTimeout(audioTimeout)
+        rotation()
+    }
+})
+
+const clickHandler = () => {
+    playIng.value = !playIng.value
+}
+
+defineExpose({
+    play() {
+        playIng.value = true
+    },
+    pause() {
+        playIng.value = false
+    },
+})
+</script>

+ 2 - 0
packages/components/basic/audio/style/css.ts

@@ -0,0 +1,2 @@
+import '@kankan-components/components/base/style/css'
+import '@kankan-components/theme-chalk/ui-audio.css'

+ 2 - 0
packages/components/basic/audio/style/index.ts

@@ -0,0 +1,2 @@
+import '@kankan-components/components/base/style'
+import '@kankan-components/theme-chalk/src/audio.scss'

+ 1 - 0
packages/components/basic/index.ts

@@ -1,2 +1,3 @@
 export * from './icon'
 export * from './button'
+export * from './audio'

+ 4 - 1
packages/kankan-components/component.ts

@@ -1,6 +1,9 @@
 import { UIIcon } from '@kankan-components/components/basic/icon'
 import { UIButton } from '@kankan-components/components/basic/button'
+import { UIAudio } from '@kankan-components/components/basic/audio'
+
 import { UITag } from '@kankan-components/components/advance/tag'
+
 import type { Plugin } from 'vue'
 
-export default [UIIcon, UIButton, UITag] as Plugin[]
+export default [UIIcon, UIButton, UIAudio, UITag] as Plugin[]

+ 21 - 0
packages/theme-chalk/src/audio.scss

@@ -0,0 +1,21 @@
+.ui-audio {
+    display: inline-block;
+    cursor: pointer;
+
+    > span {
+        --height: 18px;
+        width: 3px;
+        height: calc(var(--height) * var(--percent));
+        background: var(--colors-primary-base);
+        display: inline-block;
+        transition: height 0.2s linear;
+
+        &:not(:last-child) {
+            margin-right: 2px;
+        }
+    }
+
+    audio {
+        display: none;
+    }
+}

+ 1 - 0
packages/theme-chalk/src/index.scss

@@ -1,4 +1,5 @@
 @use './icon.scss';
 @use './button.scss';
+@use './audio.scss';
 
 @use './tag.scss';

+ 11 - 1
pnpm-lock.yaml

@@ -120,6 +120,7 @@ importers:
       '@kankan-components/utils': workspace:*
       '@types/markdown-it': ^12.2.3
       '@vitejs/plugin-vue-jsx': ^1.3.10
+      '@vue/repl': ^1.3.2
       '@vue/shared': ^3.2.37
       '@vueuse/core': ^9.1.0
       axios: ^0.27.2
@@ -129,7 +130,7 @@ importers:
       element-plus: npm:element-plus@latest
       escape-html: ^1.0.3
       fast-glob: ^3.2.11
-      kankan-components: ^0.0.1
+      kankan-components: workspace:^0.0.1
       markdown-it: ^13.0.1
       markdown-it-container: ^3.0.0
       normalize.css: ^8.0.1
@@ -152,6 +153,7 @@ importers:
       '@element-plus/icons-vue': 2.0.10_vue@3.2.39
       '@kankan-components/metadata': link:../internal/metadata
       '@kankan-components/utils': link:../packages/utils
+      '@vue/repl': 1.3.2_vue@3.2.39
       '@vue/shared': 3.2.39
       '@vueuse/core': 9.3.0_vue@3.2.39
       axios: 0.27.2
@@ -5479,6 +5481,14 @@ packages:
     dependencies:
       '@vue/shared': 3.2.47
 
+  /@vue/repl/1.3.2_vue@3.2.39:
+    resolution: {integrity: sha512-5joGOuTFmjaugG3E1h/oP1EXSMcVXRUwLIoo8xvYQnqDrCT6g1SfsH1pfei5PpC5DUxMX1584CekZu6REgGYkQ==}
+    peerDependencies:
+      vue: ^3.2.13
+    dependencies:
+      vue: 3.2.39
+    dev: false
+
   /@vue/runtime-core/3.2.39:
     resolution: {integrity: sha512-xKH5XP57JW5JW+8ZG1khBbuLakINTgPuINKL01hStWLTTGFOrM49UfCFXBcFvWmSbci3gmJyLl2EAzCaZWsx8g==}
     dependencies: