| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- <template>
- <transition name="fade-in">
- <div class="code-preview-wrapper" v-show="visible" @keydown.s.ctrl.prevent="handleModifyFileContent">
- <!-- 顶部信息栏 & 工具栏 -->
- <div class="tip-wrapper" v-if="visible">
- <div class="name" :title="$file.getFileNameComplete(fileInfo)">
- {{ $file.getFileNameComplete(fileInfo) }}
- <span class="un-save" v-show="isModify && !codeMirrorOptions.readOnly">(未保存)</span>
- </div>
- <div class="editor-preveiw">在线预览{{ codeMirrorOptions.readOnly ? '' : ' & 编辑' }}</div>
- <div class="tool-wrapper">
- <a
- class="item download-link"
- target="_blank"
- :href="$file.getDownloadFilePath(fileInfo)"
- :download="$file.getFileNameComplete(fileInfo)"
- >
- <DownloadOutlined title="下载" />
- </a>
- <a-tooltip placement="bottom">
- <template #title>
- 操作提示: <br />
- 1. 按 Esc 键可退出查看;<br />
- 2. 支持在线编辑、保存、下载
- </template>
- <div class="item text-wrapper">
- <span class="text">操作提示</span>
- <BulbOutlined />
- </div>
- </a-tooltip>
- <CloseOutlined class="item" title="关闭预览" @click="closeCodePreview" />
- </div>
- </div>
- <!-- 代码编辑区域 -->
- <div class="code-editor-wrapper">
- <div class="operate-wrapper">
- <SaveOutlined
- class="save-icon"
- title="保存(ctrl+s)"
- v-show="isModify && !codeMirrorOptions.readOnly"
- @click="handleModifyFileContent"
- />
- <a-form class="editor-set-form" :model="codeMirrorOptions" layout="inline" size="small" label-align="right">
- <a-form-item class="line-wrapper">
- <a-checkbox v-model:checked="codeMirrorOptions.lineWrapping" @change="handleChangeCodeMirrorOption"
- >自动换行</a-checkbox
- >
- </a-form-item>
- <a-form-item class="font-size">
- <a-select v-model:value="codeMirrorCustomOptions.fontSize" filterable>
- <a-select-option v-for="(item, index) in fontSizeList" :key="index" :value="item"
- >{{ item }} px</a-select-option
- >
- </a-select>
- </a-form-item>
- <a-form-item label="代码语言" class="lanaguage">
- <a-select v-model:value="codeMirrorLanguage" filterable @change="handleChangeCodeMirrorOption">
- <a-select-option v-for="[key, value] in fileSuffixCodeModeMap.entries()" :key="key" :value="key">{{
- value.language
- }}</a-select-option>
- </a-select>
- </a-form-item>
- <a-form-item label="主题" class="theme">
- <a-select v-model:value="codeMirrorTheme" filterable @change="handleChangeCodeMirrorOption">
- <a-select-option value="default">default</a-select-option>
- <a-select-option v-for="(item, index) in codeMirrorThemeList" :key="index" :value="item">{{
- item
- }}</a-select-option>
- </a-select>
- </a-form-item>
- </a-form>
- </div>
- <a-spin :spinning="codeMirrorLoading">
- <Codemirror
- class="code-editor"
- ref="codemirrorRef"
- v-model="codeMirrorText"
- :style="{ fontSize: `${codeMirrorCustomOptions.fontSize}px` }"
- :extensions="extensions"
- :disabled="codeMirrorOptions.readOnly"
- :indent-with-tab="true"
- :tab-size="codeMirrorOptions.tabSize"
- :placeholder="'请输入代码...'"
- v-if="isShow"
- />
- </a-spin>
- </div>
- </div>
- </transition>
- </template>
- <script setup>
- import { ref, reactive, computed, watch, onMounted, onUnmounted, getCurrentInstance } from 'vue'
- import { message } from 'ant-design-vue'
- import { DownloadOutlined, BulbOutlined, CloseOutlined, SaveOutlined } from '@ant-design/icons-vue'
- import { Codemirror } from 'vue-codemirror'
- import { basicSetup } from 'codemirror'
- import { oneDark } from '@codemirror/theme-one-dark'
- import { javascript } from '@codemirror/lang-javascript'
- import { html } from '@codemirror/lang-html'
- import { css } from '@codemirror/lang-css'
- import { json } from '@codemirror/lang-json'
- import { xml } from '@codemirror/lang-xml'
- // import { EditorView } from '@codemirror/view'
- import { fontSizeList, fileSuffixCodeModeMap, codeMirrorThemeList } from '@/libs/map.js'
- import { useMyResourceStore } from '@/store/myResource.js'
- import { getFilePreview, modifyFileContent } from '@/api/myResource/file.js'
- import tool from '@/utils/tool'
- const props = defineProps({
- fileInfo: Object,
- isEdit: Boolean,
- callback: Function
- })
- const myResource = useMyResourceStore()
- const visible = ref(false)
- defineExpose({ visible })
- const originalCodeText = ref('')
- const codeMirrorText = ref('')
- const codeMirrorLoading = ref(false)
- const isShow = ref(true)
- const codemirrorRef = ref(null)
- const codeMirrorLanguage = ref('html')
- const codeMirrorTheme = ref('default')
- // CodeMirror 配置
- const codeMirrorOptions = reactive({
- tabSize: 4,
- mode: 'text/html',
- theme: 'default',
- readOnly: true,
- lineNumbers: true,
- line: true,
- autoCloseBrackets: true,
- foldGutter: true,
- lineWrapping: true,
- gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers']
- })
- const codeMirrorCustomOptions = reactive({
- fontSize: 14
- })
- // 根据语言和主题生成扩展
- const extensions = computed(() => {
- const exts = [basicSetup]
- // 添加语言支持
- switch (codeMirrorLanguage.value) {
- case 'js':
- case 'javascript':
- exts.push(javascript())
- break
- case 'html':
- exts.push(html())
- break
- case 'css':
- exts.push(css())
- break
- case 'json':
- exts.push(json())
- break
- case 'xml':
- exts.push(xml())
- break
- default:
- exts.push(html())
- }
- // 添加主题
- if (codeMirrorTheme.value === 'one-dark') {
- exts.push(oneDark)
- } else {
- // 默认亮色主题
- exts.push(
- // EditorView.theme({
- // '&': {
- // height: '100%',
- // fontSize: `${codeMirrorCustomOptions.fontSize}px`,
- // color: '#e0e0e0' // 添加默认文本颜色为浅灰色
- // },
- // '.cm-scroller': {
- // overflow: 'auto'
- // },
- // '.cm-content': {
- // color: '#e0e0e0' // 确保内容区域文本为浅灰色
- // },
- // '.cm-line': {
- // color: '#e0e0e0' // 确保每行文本为浅灰色
- // },
- // // 语法高亮颜色
- // '.cm-comment': { color: '#6a9955' }, // 注释
- // '.cm-keyword': { color: '#569cd6' }, // 关键字
- // '.cm-string': { color: '#ce9178' }, // 字符串
- // '.cm-number': { color: '#b5cea8' }, // 数字
- // '.cm-property': { color: '#9cdcfe' }, // 属性
- // '.cm-operator': { color: '#d4d4d4' }, // 操作符
- // '.cm-meta': { color: '#dcdcaa' }, // 元数据
- // '.cm-atom': { color: '#4ec9b0' }, // 原子
- // '.cm-variable': { color: '#9cdcfe' }, // 变量
- // '.cm-tag': { color: '#569cd6' }, // 标签
- // '.cm-attribute': { color: '#9cdcfe' } // 属性
- // })
- )
- }
- // 添加行号
- if (codeMirrorOptions.lineNumbers) {
- // exts.push(EditorView.lineWrapping)
- }
- return exts
- })
- const screenWidth = computed(() => myResource.screenWidth)
- const isModify = computed(() => originalCodeText.value !== codeMirrorText.value)
- watch(visible, (val) => {
- if (val) {
- let fileSuffix = props.fileInfo.extendName.toLowerCase()
- if (fileSuffix === 'yaml') {
- fileSuffix = 'yml'
- }
- if (fileSuffixCodeModeMap.has(fileSuffix)) {
- codeMirrorLanguage.value = fileSuffix
- codeMirrorOptions.mode = fileSuffixCodeModeMap.get(fileSuffix).mime
- }
- codeMirrorOptions.readOnly = !props.isEdit
- codeMirrorTheme.value = localStorage.getItem('qiwen_file_codemirror_theme') || 'default'
- getCodeText()
- document.addEventListener('keyup', handleEscKey)
- } else {
- document.removeEventListener('keyup', handleEscKey)
- }
- })
- watch(
- () => codeMirrorTheme.value,
- (val) => {
- localStorage.setItem('qiwen_file_codemirror_theme', val)
- }
- )
- const getCodeText = () => {
- codeMirrorLoading.value = true
- getFilePreview({
- userFileId: props.fileInfo.userFileId,
- isMin: false,
- shareBatchNum: props.fileInfo.shareBatchNum,
- extractionCode: props.fileInfo.extractionCode,
- token: tool.data.get('TOKEN')
- })
- .then((res) => {
- codeMirrorLoading.value = false
- originalCodeText.value = typeof res === 'object' ? JSON.stringify(res) : res
- codeMirrorText.value = originalCodeText.value + ''
- })
- .catch(() => {
- codeMirrorLoading.value = false
- })
- }
- const handleModifyFileContent = () => {
- if (!isModify.value || codeMirrorOptions.readOnly) {
- return false
- }
- codeMirrorLoading.value = true
- modifyFileContent({
- userFileId: props.fileInfo.userFileId,
- fileContent: codeMirrorText.value
- })
- .then((res) => {
- codeMirrorLoading.value = false
- if (res.success) {
- message.success('已保存')
- getCodeText()
- } else {
- message.error(res.message)
- }
- })
- .catch((err) => {
- codeMirrorLoading.value = false
- message.error(err.message)
- })
- }
- const handleChangeCodeMirrorOption = () => {
- isShow.value = false
- setTimeout(() => {
- isShow.value = true
- }, 0)
- }
- const closeCodePreview = () => {
- visible.value = false
- props.callback('cancel')
- }
- const handleEscKey = (e) => {
- if (e.keyCode === 27) {
- closeCodePreview()
- }
- }
- onMounted(() => {
- if (visible.value) {
- document.addEventListener('keyup', handleEscKey)
- }
- })
- onUnmounted(() => {
- document.removeEventListener('keyup', handleEscKey)
- })
- </script>
- <style lang="less" scoped>
- @import '@/style/myResource/varibles.less';
- @import '@/style/myResource/mixins.less';
- .code-preview-wrapper {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- overflow: auto;
- width: 100%;
- height: 100vh;
- z-index: 2;
- display: flex;
- align-items: center;
- animation: imgPreviewAnimation 0.3s;
- -webkit-animation: imgPreviewAnimation 0.3s; /* Safari and Chrome */
- animation-iteration-count: 0.3;
- -webkit-animation-iteration-count: 0.3;
- animation-fill-mode: forwards;
- -webkit-animation-fill-mode: forwards; /* Safari 和 Chrome */
- @keyframes imgPreviewAnimation {
- 0% {
- background: transparent;
- }
- 100% {
- background: rgba(0, 0, 0, 0.8);
- }
- }
- .tip-wrapper {
- position: fixed;
- top: 0;
- left: 0;
- z-index: 2;
- background: rgba(0, 0, 0, 0.5);
- padding: 0 48px;
- width: 100%;
- height: 48px;
- line-height: 48px;
- color: #fff;
- font-size: 16px;
- display: flex;
- justify-content: space-between;
- .name {
- flex: 1;
- padding-right: 16px;
- text-align: left;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- .un-save {
- color: @warning-color;
- font-size: 14px;
- }
- }
- .tool-wrapper {
- flex: 1;
- display: flex;
- justify-content: flex-end;
- .item {
- margin-left: 16px;
- height: 48px;
- line-height: 48px;
- cursor: pointer;
- &:hover {
- opacity: 0.7;
- }
- }
- .download-link {
- color: inherit;
- font-size: 18px;
- }
- .text-wrapper {
- .text {
- margin-right: 8px;
- }
- }
- }
- }
- .code-editor-wrapper {
- margin: 56px auto 0 auto;
- width: 90vw;
- height: calc(100vh - 80px);
- .operate-wrapper {
- display: flex;
- justify-content: space-between;
- align-items: center;
- border-radius: 8px 8px 0 0;
- border-bottom: 1px solid @border-color-base;
- padding: 8px 16px;
- background: #1e1e1e; /* 修改为暗色背景 */
- color: #e0e0e0; /* 添加文字颜色为浅色 */
- .save-icon {
- font-size: 20px;
- cursor: pointer;
- color: @Info;
- font-weight: 550;
- &:hover {
- opacity: 0.5;
- }
- }
- .editor-set-form {
- flex: 1;
- text-align: right;
- :deep(.ant-form-item) {
- margin-bottom: 0;
- /* 修改表单项文字颜色 */
- color: #e0e0e0;
- .ant-form-item-label > label {
- color: #e0e0e0;
- }
- .ant-checkbox-wrapper {
- color: #e0e0e0;
- }
- &.font-size {
- .ant-form-item-control-input-content {
- .ant-select {
- width: 96px;
- }
- }
- }
- &.lanaguage {
- .ant-form-item-control-input-content {
- .ant-select {
- width: 120px;
- }
- }
- }
- &.theme {
- .ant-form-item-control-input-content {
- .ant-select {
- width: 190px;
- }
- }
- }
- }
- }
- }
- .code-editor {
- height: calc(100vh - 129px);
- background: #1e1e1e; /* 添加暗色背景 */
- :deep(.cm-editor) {
- border-radius: 0 0 8px 8px;
- height: inherit;
- font-size: inherit;
- background: #1e1e1e; /* 添加暗色背景 */
- color: #e0e0e0; /* 确保编辑器文本为浅灰色 */
- * {
- font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace !important;
- }
- .cm-scroller {
- width: 100%;
- padding: 8px 0 0 0;
- line-height: 1.5;
- font-size: inherit;
- overflow: auto;
- background: #1e1e1e; /* 添加暗色背景 */
- }
- .cm-content {
- color: #e0e0e0; /* 确保内容区域文本为浅灰色 */
- }
- .cm-line {
- color: #e0e0e0; /* 确保每行文本为浅灰色 */
- }
- .cm-gutters {
- background: #1e1e1e; /* 行号区域背景 */
- border-right: 1px solid #333; /* 行号右侧边框 */
- color: #858585; /* 行号颜色 */
- }
- .cm-activeLineGutter {
- background-color: #2c2c2c; /* 当前行的行号背景 */
- }
- }
- }
- }
- }
- </style>
|