Browse Source
- 在 `src/renderer/src/store/module/page.ts` 中新增 `usePageStore`,实现页面缓存管理功能,包括添加和移除缓存视图的操作。 - 创建模板页面 `src/renderer/src/pages/Template.vue`,并实现页面结构和路由逻辑。 - 新增多个模板相关组件和文件,包括 `IPhone.vue`、`MeCode` 组件及其相关逻辑,支持代码编辑和占位符功能。 - 更新导航栏,添加模板页面的导航链接。develop
22 changed files with 634 additions and 48 deletions
@ -0,0 +1,14 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="w-full h-full bg-[#E9ECEF] grid place-content-center place-items-center"> |
||||
|
<div shadow="lg" bg="light-100" |
||||
|
style="width: 375px; height: 667px; transform: scale(1); transform-origin: center center;"> |
||||
|
<slot></slot> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped></style> |
||||
@ -0,0 +1,59 @@ |
|||||
|
import { monaco } from "./monaco" |
||||
|
|
||||
|
/** |
||||
|
* Represents a placeholder renderer for monaco editor |
||||
|
* Roughly based on https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint/untitledTextEditorHint.ts
|
||||
|
*/ |
||||
|
export class PlaceholderContentWidget implements monaco.editor.IContentWidget { |
||||
|
private static readonly ID = "editor.widget.placeholderHint" |
||||
|
|
||||
|
private domNode: HTMLElement | undefined |
||||
|
|
||||
|
constructor( |
||||
|
private readonly placeholder: (() => Node) | string, |
||||
|
private readonly editor: monaco.editor.ICodeEditor, |
||||
|
) { |
||||
|
// register a listener for editor code changes
|
||||
|
editor.onDidChangeModelContent(() => this.onDidChangeModelContent()) |
||||
|
// ensure that on initial load the placeholder is shown
|
||||
|
this.onDidChangeModelContent() |
||||
|
} |
||||
|
|
||||
|
private onDidChangeModelContent(): void { |
||||
|
if (this.editor.getValue() === "") { |
||||
|
this.editor.addContentWidget(this) |
||||
|
} else { |
||||
|
this.editor.removeContentWidget(this) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getId(): string { |
||||
|
return PlaceholderContentWidget.ID |
||||
|
} |
||||
|
|
||||
|
getDomNode(): HTMLElement { |
||||
|
if (!this.domNode) { |
||||
|
this.domNode = document.createElement("div") |
||||
|
this.domNode.style.width = "max-content" |
||||
|
this.domNode.style.pointerEvents = "none" // 整个容器禁用指针事件
|
||||
|
this.domNode.style.fontStyle = "italic" |
||||
|
this.domNode.style.opacity = "0.6" // 添加透明度,更像 placeholder
|
||||
|
|
||||
|
this.domNode.appendChild(typeof this.placeholder === "function" ? this.placeholder() : document.createTextNode(this.placeholder)) |
||||
|
this.editor.applyFontInfo(this.domNode) |
||||
|
} |
||||
|
|
||||
|
return this.domNode |
||||
|
} |
||||
|
|
||||
|
getPosition(): monaco.editor.IContentWidgetPosition | null { |
||||
|
return { |
||||
|
position: { lineNumber: 1, column: 1 }, |
||||
|
preference: [monaco.editor.ContentWidgetPositionPreference.EXACT], |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
dispose(): void { |
||||
|
this.editor.removeContentWidget(this) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,211 @@ |
|||||
|
import { monaco } from "./monaco" |
||||
|
import { PlaceholderContentWidget } from "./PlaceholderContentWidget" |
||||
|
import { judgeFile } from "./utils" |
||||
|
import type { Ref } from "vue" |
||||
|
|
||||
|
function useResizeObserver(el: HTMLDivElement, callback: ResizeObserverCallback) { |
||||
|
const isSupported = window && "ResizeObserver" in window |
||||
|
let observer: ResizeObserver | undefined |
||||
|
const cleanup = () => { |
||||
|
if (observer) { |
||||
|
observer.disconnect() |
||||
|
observer = undefined |
||||
|
} |
||||
|
} |
||||
|
const stopWatch = watch( |
||||
|
() => el, |
||||
|
el => { |
||||
|
cleanup() |
||||
|
if (isSupported && window && el) { |
||||
|
observer = new ResizeObserver(callback) |
||||
|
observer!.observe(el, {}) |
||||
|
} |
||||
|
}, |
||||
|
{ immediate: true }, |
||||
|
) |
||||
|
const stop = () => { |
||||
|
cleanup() |
||||
|
stopWatch() |
||||
|
} |
||||
|
function tryOnScopeDispose(fn: () => void) { |
||||
|
if (getCurrentScope()) { |
||||
|
onScopeDispose(fn) |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
tryOnScopeDispose(() => { |
||||
|
stop() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
export interface IOptions { |
||||
|
placeholder: (() => Node) | string | undefined |
||||
|
filename: string |
||||
|
extendsExt?: {language: string, ext?: string, pre?: string}[] |
||||
|
content: string |
||||
|
editorOptions: monaco.editor.IEditorOptions & monaco.editor.IGlobalEditorOptions |
||||
|
modelOptions: monaco.editor.ITextModelUpdateOptions |
||||
|
onCursorChange?: (e: monaco.editor.ICursorPositionChangedEvent) => void |
||||
|
onDidChangeContent?: (str: string) => void |
||||
|
} |
||||
|
|
||||
|
const defaultOptions: IOptions = { |
||||
|
placeholder: undefined, |
||||
|
filename: "temp", |
||||
|
extendsExt: [], |
||||
|
content: "", |
||||
|
editorOptions: { |
||||
|
fontSize: 14, |
||||
|
readOnly: false, |
||||
|
theme: "vs-light", |
||||
|
fontFamily: "Cascadia Mono, Consolas, 'Courier New', monospace", |
||||
|
scrollBeyondLastLine: false, |
||||
|
lineHeight: 22, |
||||
|
automaticLayout: true, |
||||
|
minimap: { |
||||
|
enabled: false, |
||||
|
}, |
||||
|
}, |
||||
|
modelOptions: {}, |
||||
|
} |
||||
|
|
||||
|
const assign = (curOpt, opt, parenyKey: string[] = [], config = { arrayExtend: "concat" }) => { |
||||
|
for (const key in opt) { |
||||
|
if (opt[key] !== undefined) { |
||||
|
if (typeof opt[key] === "function" && curOpt[key] !== undefined && typeof curOpt[key] !== "function") { |
||||
|
opt[key] = opt[key](curOpt[key]) |
||||
|
} |
||||
|
if (typeof opt[key] === "object" && !Array.isArray(opt[key]) && !Array.isArray(curOpt[key])) { |
||||
|
parenyKey.push(key) |
||||
|
assign(curOpt[key], opt[key], parenyKey, config) |
||||
|
} else if (typeof opt[key] === "object" && Array.isArray(opt[key]) && Array.isArray(curOpt[key])) { |
||||
|
if (config.arrayExtend === "concat") { |
||||
|
curOpt[key] = curOpt[key].concat(opt[key]) |
||||
|
} else { |
||||
|
curOpt[key] = opt[key] |
||||
|
} |
||||
|
} else if (curOpt[key] !== undefined && typeof curOpt[key] !== typeof opt[key]) { |
||||
|
throw new Error(`Type of ${parenyKey.join(",") + "." + key} is not match`) |
||||
|
} else { |
||||
|
curOpt[key] = opt[key] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return curOpt |
||||
|
} |
||||
|
|
||||
|
function getOptions(opt = {}, config = { arrayExtend: "concat" }): IOptions { |
||||
|
const curOptions = structuredClone(defaultOptions) |
||||
|
assign(curOptions, opt, [], config) |
||||
|
return curOptions |
||||
|
} |
||||
|
|
||||
|
export function useMonacoEditor(editorElement: Ref<HTMLDivElement | undefined>, opts: Partial<IOptions>) { |
||||
|
let curOption = getOptions(opts) as IOptions |
||||
|
let editor: monaco.editor.IStandaloneCodeEditor | null = null |
||||
|
let placeholderWidget: PlaceholderContentWidget | null = null |
||||
|
|
||||
|
const updateOption = (opts: Partial<IOptions>) => { |
||||
|
if (!editor) return |
||||
|
curOption = assign(curOption, opts) |
||||
|
if (Object.hasOwn(opts, "placeholder")) { |
||||
|
if (placeholderWidget) { |
||||
|
placeholderWidget.dispose() |
||||
|
placeholderWidget = null |
||||
|
} |
||||
|
if (opts.placeholder) { |
||||
|
placeholderWidget = new PlaceholderContentWidget(opts.placeholder, editor) |
||||
|
} |
||||
|
} |
||||
|
if (Object.hasOwn(opts, "modelOptions") && opts.modelOptions) { |
||||
|
const model = editor.getModel() |
||||
|
model?.updateOptions(opts.modelOptions) |
||||
|
} |
||||
|
if (Object.hasOwn(opts, "editorOptions") && opts.editorOptions) { |
||||
|
editor.updateOptions(opts.editorOptions) |
||||
|
} |
||||
|
if (Object.hasOwn(opts, "filename")) { |
||||
|
updateModel(curOption.filename, curOption.content) |
||||
|
} |
||||
|
if (Object.hasOwn(opts, "content")) { |
||||
|
console.log("无法通过updateOption修改content") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
let isInnerChange = "waitting" // waitting, out, in
|
||||
|
const setValue = (content: string) => { |
||||
|
if (isInnerChange === "waitting") { |
||||
|
isInnerChange = "out" |
||||
|
} |
||||
|
if (editor && isInnerChange === "out") { |
||||
|
editor.setValue(content) |
||||
|
} else { |
||||
|
isInnerChange = "waitting" |
||||
|
} |
||||
|
} |
||||
|
function updateModel(name: string, content: string) { |
||||
|
if (editor) { |
||||
|
const oldModel = editor.getModel() //获取旧模型
|
||||
|
const file = judgeFile(name, curOption.extendsExt || []) |
||||
|
// 这样定义的话model无法清除
|
||||
|
// monaco.editor.createModel("const a = 111","typescript", monaco.Uri.parse('file://root/file3.ts'))
|
||||
|
const model: monaco.editor.ITextModel = monaco.editor.createModel(content ?? "", file?.language ?? "txt") |
||||
|
model.updateOptions(curOption.modelOptions) |
||||
|
model.onDidChangeContent(() => { |
||||
|
if (model) { |
||||
|
if (isInnerChange === "out") { |
||||
|
isInnerChange = "waitting" |
||||
|
return |
||||
|
} |
||||
|
isInnerChange = "in" |
||||
|
const code = model.getValue() |
||||
|
curOption.onDidChangeContent?.(code) |
||||
|
} |
||||
|
}) |
||||
|
if (oldModel) { |
||||
|
oldModel.dispose() |
||||
|
} |
||||
|
editor.setModel(model) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
tryOnMounted(() => { |
||||
|
if (editorElement.value && !editor) { |
||||
|
editor = monaco.editor.create(editorElement.value, curOption.editorOptions) as monaco.editor.IStandaloneCodeEditor |
||||
|
editor.onDidChangeCursorPosition(e => { |
||||
|
curOption.onCursorChange?.(e) |
||||
|
}) |
||||
|
if (!curOption.content) { |
||||
|
placeholderWidget = new PlaceholderContentWidget(curOption.placeholder || "", editor) |
||||
|
} else { |
||||
|
if (isInnerChange === "waitting") { |
||||
|
isInnerChange = "out" |
||||
|
} |
||||
|
} |
||||
|
updateModel(curOption.filename, curOption.content) |
||||
|
useResizeObserver(editorElement.value, () => { |
||||
|
editor!.layout() |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
tryOnUnmounted(() => { |
||||
|
if (editor) { |
||||
|
const oldModel = editor.getModel() |
||||
|
if (oldModel) { |
||||
|
oldModel.dispose() |
||||
|
} |
||||
|
editor.dispose() |
||||
|
editor = null |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
return { |
||||
|
setValue, |
||||
|
scrollTop() { |
||||
|
editor?.setScrollTop(0) |
||||
|
}, |
||||
|
updateOption, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { useMonacoEditor, IOptions } from "./hook" |
||||
|
|
||||
|
const props = withDefaults( |
||||
|
defineProps<{ |
||||
|
readonly?: boolean |
||||
|
modelValue?: string |
||||
|
filename?: string |
||||
|
placeholder?: (() => Node) | string |
||||
|
extendsExt?: {language: string, ext?: string, pre?: string}[] |
||||
|
modelOptions?: IOptions["modelOptions"] |
||||
|
editorOptions?: IOptions["editorOptions"] |
||||
|
}>(), |
||||
|
{ |
||||
|
readonly: false, |
||||
|
modelValue: "", |
||||
|
filename: "", |
||||
|
}, |
||||
|
) |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
(e: "update:modelValue", code: string): void |
||||
|
(e: "change", code: string): void |
||||
|
(e: "cursor:position", position: [number, number]): void |
||||
|
}>() |
||||
|
|
||||
|
const editorRef = ref<HTMLDivElement>() |
||||
|
const { updateOption, setValue } = useMonacoEditor(editorRef, { |
||||
|
placeholder: props.placeholder, |
||||
|
content: props.modelValue, |
||||
|
filename: props.filename, |
||||
|
extendsExt: props.extendsExt, |
||||
|
modelOptions: props.modelOptions, |
||||
|
editorOptions: props.editorOptions, |
||||
|
onCursorChange(e) { |
||||
|
emit("cursor:position", [e.position.lineNumber, e.position.column]) |
||||
|
}, |
||||
|
onDidChangeContent(code) { |
||||
|
emit("update:modelValue", code) |
||||
|
emit("change", code) |
||||
|
}, |
||||
|
}) |
||||
|
watch( |
||||
|
() => props.modelValue, |
||||
|
() => { |
||||
|
setValue(props.modelValue) |
||||
|
}, |
||||
|
) |
||||
|
watch( |
||||
|
() => props.filename, |
||||
|
() => { |
||||
|
updateOption({ |
||||
|
filename: props.filename, |
||||
|
}) |
||||
|
}, |
||||
|
) |
||||
|
watch( |
||||
|
() => props.editorOptions, |
||||
|
() => { |
||||
|
updateOption({ |
||||
|
editorOptions: props.editorOptions, |
||||
|
}) |
||||
|
}, |
||||
|
{ deep: true }, |
||||
|
) |
||||
|
watch( |
||||
|
() => props.modelOptions, |
||||
|
() => { |
||||
|
updateOption({ |
||||
|
modelOptions: props.modelOptions, |
||||
|
}) |
||||
|
}, |
||||
|
{ deep: true }, |
||||
|
) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="monaco-wrapper"> |
||||
|
<div ref="editorRef" class="monaco-editor"></div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.monaco-wrapper { |
||||
|
height: 100%; |
||||
|
position: relative; |
||||
|
|
||||
|
.monaco-editor { |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.monaco-bg { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
pointer-events: none; |
||||
|
opacity: 0.1; |
||||
|
overflow: hidden; |
||||
|
|
||||
|
.monaco-logo { |
||||
|
@apply absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,19 @@ |
|||||
|
// import 'monaco-editor/esm/vs/editor/editor.all.js';
|
||||
|
|
||||
|
// import 'monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.js';
|
||||
|
|
||||
|
// import 'monaco-editor/esm/vs/basic-languages/monaco.contribution.js';
|
||||
|
|
||||
|
// import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution.js';
|
||||
|
// import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js';
|
||||
|
// import 'monaco-editor/esm/vs/basic-languages/html/html.contribution.js';
|
||||
|
// import 'monaco-editor/esm/vs/basic-languages/css/css.contribution.js';
|
||||
|
// import 'monaco-editor/esm/vs/basic-languages/java/java.contribution.js';
|
||||
|
|
||||
|
// 导入全部特性
|
||||
|
import * as monaco from "monaco-editor" |
||||
|
|
||||
|
// import * as monaco from "monaco-editor/esm/vs/editor/editor.api"
|
||||
|
// import "monaco-editor/esm/vs/basic-languages/monaco.contribution.js"
|
||||
|
|
||||
|
export { monaco } |
||||
@ -0,0 +1,33 @@ |
|||||
|
export function judgeFile(filename: string, extendsExt?: {language: string, ext?: string, pre?: string}[]) { |
||||
|
if (!filename) return |
||||
|
const ext = [ |
||||
|
{ language: "vue", ext: ".vue", index: -1 }, |
||||
|
{ language: "javascript", ext: ".js", index: -1 }, |
||||
|
{ language: "css", ext: ".css", index: -1 }, |
||||
|
{ language: "html", ext: ".html", index: -1 }, |
||||
|
{ language: "tsx", ext: ".tsx", index: -1 }, |
||||
|
{ language: "typescript", ext: ".ts", index: -1 }, |
||||
|
{ language: "markdown", ext: ".md", index: -1 }, |
||||
|
{ language: "json", ext: ".json", index: -1 }, |
||||
|
{ language: "web", ext: ".web", index: -1 }, |
||||
|
{ language: "dot", pre: ".", index: -1 }, |
||||
|
...(extendsExt || []).map(e => ({ ...e, index: -1 })), |
||||
|
] |
||||
|
let cur |
||||
|
for (let i = 0; i < ext.length; i++) { |
||||
|
const e = ext[i] |
||||
|
if (e.ext && filename.endsWith(e.ext)) { |
||||
|
const index = filename.lastIndexOf(e.ext) |
||||
|
e.index = index |
||||
|
cur = e |
||||
|
break |
||||
|
} |
||||
|
if (e.pre && filename.startsWith(e.pre)) { |
||||
|
const index = filename.indexOf(e.pre) |
||||
|
e.index = index |
||||
|
cur = e |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
return cur |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { moduleArray } from "./Template/all" |
||||
|
let store = usePageStore() |
||||
|
const cacheList = computed(() => store.cache) |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="flex h-full dev-filetree"> |
||||
|
<div class="w-200px px-5 py-5 flex flex-col gap-1 bg-white relative" style="border-right: 1px solid #e5e8ea"> |
||||
|
<router-link to="/Template"> |
||||
|
首页 |
||||
|
</router-link> |
||||
|
<router-link class="truncate" :title="item.title" :to="item.url" v-for="item in moduleArray" |
||||
|
:key="item.title"> |
||||
|
{{ item.title }} |
||||
|
</router-link> |
||||
|
<AdjustLine></AdjustLine> |
||||
|
</div> |
||||
|
<div class="flex-1 w-0 relative"> |
||||
|
<router-view v-slot="{ Component, route }"> |
||||
|
<!-- 缓存界面 --> |
||||
|
<keep-alive :include="cacheList"> |
||||
|
<component :key="route.meta.parentPath ? route.meta.parentPath : route.fullPath" :is="Component" /> |
||||
|
</keep-alive> |
||||
|
<div class="flex items-center justify-center h-full text-2xl" v-if="!Component">请选中侧边栏查看详情</div> |
||||
|
</router-view> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.dev-filetree { |
||||
|
:deep(.router-link-exact-active) { |
||||
|
color: red; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<template> |
||||
|
<IPhone> |
||||
|
asdd |
||||
|
</IPhone> |
||||
|
</template> |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"url": "/Template/Canvas", |
||||
|
"title": "Canvas" |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
const allModules = import.meta.glob("./*/params.json", { eager: true }) |
||||
|
|
||||
|
export const moduleArray: any[] = [] |
||||
|
|
||||
|
for (const key in allModules) { |
||||
|
const m = allModules[key] as any |
||||
|
const mod = m.default || m |
||||
|
moduleArray.push(mod) |
||||
|
} |
||||
|
|
||||
|
console.log(moduleArray); |
||||
|
|
||||
|
export { |
||||
|
allModules, |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
// import { useUpdaterStore } from "common/event/Updater/hook" |
||||
|
|
||||
|
// const UpdaterStore = useUpdaterStore() |
||||
|
|
||||
|
const code = ref("") |
||||
|
const aa = ref(true) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div gap="20px" h-full> |
||||
|
<input type="text" v-model="code" placeholder="请输入文本" class="input" /> |
||||
|
<input type="checkbox" v-model="aa" /> |
||||
|
{{ aa }} |
||||
|
<div h-300px> |
||||
|
<code-editor-plus :editorOptions="{ readOnly: aa }" filename="test.js" v-model="code" placeholder="请输入文本"></code-editor-plus> |
||||
|
</div> |
||||
|
<div p="20px" flex flex-wrap items-start gap="20px" justify-start rounded> |
||||
|
<Card v-for="i in 20" :key="i"></Card> |
||||
|
<button class="button">Button</button> |
||||
|
<!-- <button @click="UpdaterStore.checkForUpdates()">更新</button> --> |
||||
|
<button @click="$router.push('/browser')">浏览器</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped></style> |
||||
@ -0,0 +1,53 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
// import { useUpdaterStore } from "common/event/Updater/hook" |
||||
|
|
||||
|
// const UpdaterStore = useUpdaterStore() |
||||
|
|
||||
|
const code = ref("") |
||||
|
const aa = ref(true) |
||||
|
const placeholder = function(){ |
||||
|
const fragment = document.createDocumentFragment() |
||||
|
// 创建文本节点 |
||||
|
const textNode = document.createTextNode("请输入文本" + " ") |
||||
|
fragment.appendChild(textNode) |
||||
|
|
||||
|
// 创建链接 |
||||
|
const link = document.createElement("a") |
||||
|
link.href = "https://baidu.com" |
||||
|
link.textContent = "AA阿松大" |
||||
|
link.style.pointerEvents = "auto" // 只对链接启用指针事件 |
||||
|
link.style.color = "#0066cc" // 设置链接颜色 |
||||
|
link.style.textDecoration = "underline" |
||||
|
link.style.cursor = "pointer" |
||||
|
link.target = "_blank" // 在新标签页打开 |
||||
|
link.rel = "noopener noreferrer" // 安全属性 |
||||
|
|
||||
|
// 阻止点击链接时编辑器获得焦点 |
||||
|
link.addEventListener("mousedown", e => { |
||||
|
e.preventDefault() |
||||
|
e.stopPropagation() |
||||
|
}) |
||||
|
|
||||
|
link.addEventListener("click", e => { |
||||
|
e.preventDefault() |
||||
|
e.stopPropagation() |
||||
|
// window.open(link.href, "_blank", "noopener,noreferrer") |
||||
|
}) |
||||
|
|
||||
|
fragment.appendChild(link) |
||||
|
return fragment |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div gap="20px" h-full> |
||||
|
<input type="text" v-model="code" placeholder="请输入文本" class="input" /> |
||||
|
<input type="checkbox" v-model="aa" /> |
||||
|
{{ aa }}📁 |
||||
|
<div h-300px> |
||||
|
<me-code :editorOptions="{ readOnly: aa }" filename="test.ts" v-model="code" :placeholder="placeholder"></me-code> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped></style> |
||||
@ -0,0 +1,24 @@ |
|||||
|
import { defineStore } from "pinia" |
||||
|
|
||||
|
export const usePageStore = defineStore("page", { |
||||
|
state: (): { _cache: string[] } => ({ |
||||
|
_cache: [], |
||||
|
}), |
||||
|
getters: { |
||||
|
cache: state => state._cache, |
||||
|
}, |
||||
|
actions: { |
||||
|
addCacheView(name: string) { |
||||
|
if (!this._cache.includes(name)) { |
||||
|
this._cache.push(name) |
||||
|
} |
||||
|
}, |
||||
|
removeCacheView(name: string) { |
||||
|
let index = this.cache.indexOf(name) |
||||
|
if (index > -1) { |
||||
|
this._cache.splice(index, 1) |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
}) |
||||
|
export default usePageStore |
||||
Loading…
Reference in new issue