tiny-vue_version0/examples/sites/playground/App.vue

353 lines
11 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { onMounted, reactive, nextTick } from 'vue'
import { Repl, useStore, File } from '@opentiny/vue-repl'
import '@opentiny/vue-repl/dist/style.css'
import Editor from '@vue/repl/codemirror-editor'
import {
ButtonGroup as TinyButtonGroup,
Select as TinySelect,
Option as TinyOption,
Switch as TinySwitch,
Notify
} from '@opentiny/vue'
import { staticDemoPath, getWebdocPath } from '@/views/components/cmp-config'
import { fetchDemosFile } from '@/tools/utils'
import logoUrl from './assets/opentiny-logo.svg?url'
import GitHub from './icons/Github.vue'
import Share from './icons/Share.vue'
const VERSION = 'tiny-vue-version-3.16'
const LAYOUT = 'playground-layout'
const LAYOUT_REVERSE = 'playground-layout-reverse'
const searchObj = new URLSearchParams(location.search)
const tinyMode = searchObj.get('mode')
const tinyTheme = searchObj.get('theme')
const isMobileFirst = tinyMode === 'mobile-first'
const isSaas = tinyTheme === 'saas'
const isPreview = searchObj.get('openMode') === 'preview' // 是否多端弹窗预览
const versions = ['3.17', '3.16', '3.15', '3.14', '3.13', '3.12', '3.11', '3.10', '3.9', '3.8']
const latestVersion = isPreview ? versions[0] : localStorage.getItem(VERSION) || versions[0]
const cdnHost = localStorage.getItem('setting-cdn')
const versionDelimiter = cdnHost.includes('npmmirror') ? '/' : '@'
const fileDelimiter = cdnHost.includes('npmmirror') ? 'files/' : ''
const getRuntime = (version) => {
const useVersion = import.meta.env.VITE_PLAYGROUND_VERIOSN || version
return `${cdnHost}/@opentiny/vue-runtime${versionDelimiter}${useVersion}/${fileDelimiter}dist3/`
}
const createImportMap = (version) => {
const imports = {
'vue': `${cdnHost}/vue${versionDelimiter}3.4.27/${fileDelimiter}dist/vue.runtime.esm-browser.js`,
'echarts': `${cdnHost}/echarts${versionDelimiter}5.4.1/${fileDelimiter}dist/echarts.esm.js`,
'@vue/compiler-sfc': `${cdnHost}/@vue/compiler-sfc${versionDelimiter}3.4.27/${fileDelimiter}dist/compiler-sfc.esm-browser.js`,
'@opentiny/vue': `${getRuntime(version)}tiny-vue-pc.mjs`,
'@opentiny/vue-icon': `${getRuntime(version)}tiny-vue-icon.mjs`,
'@opentiny/vue-locale': `${getRuntime(version)}tiny-vue-locale.mjs`,
'@opentiny/vue-common': `${getRuntime(version)}tiny-vue-common.mjs`,
'@opentiny/vue-theme/': `${cdnHost}/@opentiny/vue-theme${versionDelimiter}${version}/${fileDelimiter}`,
'@opentiny/vue-theme-mobile/': `${cdnHost}/@opentiny/vue-theme-mobile${versionDelimiter}${version}/${fileDelimiter}`,
'@opentiny/vue-renderless/': `${cdnHost}/@opentiny/vue-renderless${versionDelimiter}${version}/${fileDelimiter}`,
'sortablejs': `${cdnHost}/sortablejs${versionDelimiter}1.15.0/${fileDelimiter}modular/sortable.esm.js`
}
if (['aurora', 'smb', 'saas'].includes(tinyTheme)) {
imports[`@opentiny/vue-design-${tinyTheme}`] =
`${cdnHost}/@opentiny/vue-design-${tinyTheme}${versionDelimiter}${version}/${fileDelimiter}index.js`
}
if (isSaas) {
imports['@opentiny/vue-icon'] = `${getRuntime(version)}tiny-vue-icon-saas.mjs`
}
return {
imports
}
}
const getTinyTheme = (version) => {
if (isMobileFirst) {
return `${getRuntime(version)}tailwind.css`
}
let theme = tinyTheme
if (!['smb', 'default', 'aurora', 'saas'].includes(theme)) {
theme = 'default'
}
const tinyThemeMap = {
default: `${cdnHost}/@opentiny/vue-theme${versionDelimiter}${version}/${fileDelimiter}index.css`,
smb: `${cdnHost}/@opentiny/vue-theme${versionDelimiter}${version}/${fileDelimiter}index.css`,
aurora: `${cdnHost}/@opentiny/vue-theme${versionDelimiter}${version}/${fileDelimiter}index.css`,
saas: `${cdnHost}/@opentiny/vue-theme-saas${versionDelimiter}${version}/${fileDelimiter}index.css`
}
return tinyThemeMap[theme]
}
// 如果hash有效它格式为 3.8.4|eNqIVV9p.............
const hash = location.hash.slice(1)
const shareData = hash.split('|')
// eslint-disable-next-line new-cap
const store = new useStore({
serializedState: shareData.length === 2 ? shareData[1] : '',
showOutput: true,
outputMode: 'preview',
versions: {
vue: '3.2.47',
opentiny: '3.9.1',
typescript: '5.1.3'
}
})
// repl 属性
const state = reactive({
layout: localStorage.getItem(LAYOUT) || 'horizon',
layoutReverse: localStorage.getItem(LAYOUT_REVERSE) === 'true',
layoutOptions: [
{ value: 'horizon', text: '水平' },
{ value: 'vertical', text: '垂直' }
],
// 版本切换
versions: versions.map((item) => ({ value: item })),
selectVersion: shareData.length === 2 ? shareData[0] : latestVersion,
previewOptions: {}
})
const designThemeMap = {
aurora: 'tinyAuroraTheme',
infinity: 'tinyInfinityTheme',
smb: 'tinySmbTheme'
}
function setTinyDesign() {
let importCode = ''
let useCode = ''
if (isMobileFirst) {
useCode += 'app.provide("TinyMode", "mobile-first");\n'
}
if (['aurora', 'smb', 'saas'].includes(tinyTheme)) {
importCode += `import designConfig from '@opentiny/vue-design-${tinyTheme}';
import { design } from '@opentiny/vue-common';\n`
useCode += 'app.provide(design.configKey, designConfig);\n'
}
if (['aurora', 'infinity', 'smb'].includes(tinyTheme)) {
const designTheme = designThemeMap[tinyTheme]
importCode += `import TinyThemeTool from '@opentiny/vue-theme/theme-tool';
import { ${designTheme} } from '@opentiny/vue-theme/theme';\n`
useCode += `const theme = new TinyThemeTool(${designTheme});\n`
}
state.previewOptions.customCode = {
importCode,
useCode
}
}
function selectVersion(version) {
versionChange(version)
localStorage.setItem(VERSION, version)
}
function versionChange(version) {
const importMap = createImportMap(version)
store.state.files['import-map.json'] = new File('', JSON.stringify(importMap))
insertStyleDom(version)
}
function insertStyleDom(version) {
nextTick(() => {
if (!document.querySelector('iframe')) return
const iframeWin = document.querySelector('iframe').contentWindow
const link = iframeWin.document.createElement('link')
link.id = 'tiny-theme'
link.rel = 'stylesheet'
link.href = getTinyTheme(version)
iframeWin.addEventListener('DOMContentLoaded', () => {
iframeWin.document.head.append(link)
// 增加mobile支持增加mobile的样式表
const mobileLink = link.cloneNode(true)
mobileLink.href = `${cdnHost}/@opentiny/vue-theme-mobile${versionDelimiter}${version}/${fileDelimiter}index.css`
iframeWin.document.head.append(mobileLink)
})
})
}
function changeLayout(layout) {
localStorage.setItem(LAYOUT, layout)
}
function changeReserve(isReserve) {
insertStyleDom(state.selectVersion)
localStorage.setItem(LAYOUT_REVERSE, isReserve)
}
function getDemoName(name, apiMode) {
return name.replace(/\.vue$/, `${apiMode === 'Options' ? '' : '-composition-api'}.vue`)
}
// eslint-disable-next-line unused-imports/no-unused-vars
const getDemoCode = async ({ cmpId, fileName, apiMode, mode }) => {
const demoName = getDemoName(`${getWebdocPath(cmpId)}/${fileName}`, apiMode)
const path = tinyMode === 'mobile-first' ? `@demos/mobile-first/app/${demoName}` : `${staticDemoPath}/${demoName}`
const code = await fetchDemosFile(path)
.then((code) => {
return code
})
.catch(() => {
return `${demoName}示例资源不存在,请检查文件名是否正确?`
})
return code
}
const loadFileCode = async ({ cmpId, fileName, apiMode, mode }) => {
const code = await getDemoCode({ cmpId, fileName, apiMode, mode })
store.state.mainFile = fileName
store.state.activeFile = fileName
store.addFile(new File(fileName, code, false))
versionChange(latestVersion)
}
onMounted(() => {
setTinyDesign()
// 初始加载,有分享则加载分享,否则加载默认版本的默认文件
if (shareData.length === 2) {
const demoFile = Object.values(store.state.files).find(
(file) =>
file.filename.startsWith('src/') &&
file.filename.endsWith('.vue') &&
file.filename !== 'src/App.vue' &&
file.filename !== 'src/PlaygroundMain.vue'
)
store.state.mainFile = demoFile.filename
store.state.activeFile = demoFile
versionChange(shareData[0])
} else {
const fileName = searchObj.get('fileName')
const cmpId = searchObj.get('cmpId')
const apiMode = searchObj.get('apiMode')
const mode = searchObj.get('mode')
if (fileName && cmpId && apiMode) {
loadFileCode({ cmpId, fileName, apiMode, mode })
}
}
})
// 分享功能
function share() {
const hash = store.serialize()
const shareUrl =
location.origin +
`${import.meta.env.VITE_PLAYGROUND_URL}?mode=${tinyMode}&theme=${tinyTheme}#` +
state.selectVersion +
'|' +
hash
navigator.clipboard.writeText(shareUrl)
Notify({
type: 'success',
title: '分享',
message: '当前URL已被复制到剪贴板.',
duration: 2000
})
}
</script>
<template>
<div class="header">
<div class="title"><img class="logo" :src="logoUrl" /> <span>OpenTiny Vue 演练场</span></div>
<div>
<span class="ml20">
布局方向:
<tiny-button-group
:data="state.layoutOptions"
v-model="state.layout"
@change="changeLayout"
></tiny-button-group>
</span>
<span class="ml20">
布局反转:
<tiny-switch v-model="state.layoutReverse" mini @change="changeReserve"></tiny-switch>
</span>
<span class="ml20">
OpenTiny Vue 版本:
<tiny-select v-model="state.selectVersion" @change="selectVersion" style="width: 150px" :disabled="isPreview">
<tiny-option v-for="item in state.versions" :key="item.value" :label="item.value" :value="item.value">
</tiny-option>
</tiny-select>
</span>
<Share @click="share" title="分享" class="share" />
<a style="display: flex" href="https://github.com/opentiny/tiny-vue" target="_blank">
<GitHub class="github" />
</a>
</div>
</div>
<Repl
:editor="Editor"
:store="store"
:preview-options="state.previewOptions"
:clear-console="false"
:layout="state.layout"
:layout-reverse="state.layoutReverse"
></Repl>
</template>
<style>
* {
box-sizing: border-box;
}
#header {
display: none;
}
#app {
padding-top: 0;
}
.header {
height: 46px;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-bottom: 6px;
border-bottom: solid 1px #e1e1e1;
}
.header > div {
display: flex;
align-items: center;
}
.logo {
margin-right: 10px;
}
.title {
font-size: 20px;
}
.ml20 {
margin-left: 20px;
}
.vue-repl {
/* 16px 是body默认margin*/
height: calc(100vh - 36px - 16px) !important;
}
.github,
.share {
width: 20px;
margin-right: 10px;
cursor: pointer;
}
.share {
margin-left: 10px;
}
</style>