添加批阅功能
This commit is contained in:
parent
892830bf59
commit
ce86a4d849
|
@ -0,0 +1,39 @@
|
||||||
|
<template>
|
||||||
|
<Tinymce v-bind="bindProps" @change="onChange" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import { Tinymce } from '/@/components/Tinymce/index2';
|
||||||
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'JEditor',
|
||||||
|
// 不将 attrs 的属性绑定到 html 标签上
|
||||||
|
inheritAttrs: false,
|
||||||
|
components: { Tinymce },
|
||||||
|
props: {
|
||||||
|
value: propTypes.string.def(''),
|
||||||
|
disabled: propTypes.bool.def(false),
|
||||||
|
},
|
||||||
|
emits: ['change', 'update:value'],
|
||||||
|
setup(props, { emit, attrs }) {
|
||||||
|
// 合并 props 和 attrs
|
||||||
|
const bindProps = computed(() => Object.assign({}, props, attrs));
|
||||||
|
|
||||||
|
// value change 事件
|
||||||
|
function onChange(value) {
|
||||||
|
emit('change', value);
|
||||||
|
emit('update:value', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
bindProps,
|
||||||
|
onChange,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { withInstall } from '/@/utils/index';
|
||||||
|
import tinymce from './src/Editor2.vue';
|
||||||
|
|
||||||
|
export const Tinymce = withInstall(tinymce);
|
|
@ -90,7 +90,7 @@
|
||||||
height: {
|
height: {
|
||||||
type: [Number, String] as PropType<string | number>,
|
type: [Number, String] as PropType<string | number>,
|
||||||
required: false,
|
required: false,
|
||||||
default: 400,
|
default: 220,
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
type: [Number, String] as PropType<string | number>,
|
type: [Number, String] as PropType<string | number>,
|
||||||
|
|
|
@ -0,0 +1,331 @@
|
||||||
|
<template>
|
||||||
|
<div :class="prefixCls" :style="{ width: containerWidth }">
|
||||||
|
<ImgUpload
|
||||||
|
:fullscreen="fullscreen"
|
||||||
|
@uploading="handleImageUploading"
|
||||||
|
@done="handleDone"
|
||||||
|
v-if="showImageUpload"
|
||||||
|
v-show="editorRef"
|
||||||
|
:disabled="disabled"
|
||||||
|
/>
|
||||||
|
<textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden' }" v-if="!initOptions.inline"></textarea>
|
||||||
|
<slot v-else></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Editor, RawEditorSettings } from 'tinymce2';
|
||||||
|
import tinymce from 'tinymce/tinymce';
|
||||||
|
import { defineComponent, computed, nextTick, ref, unref, watch, onDeactivated, onBeforeUnmount } from 'vue';
|
||||||
|
import ImgUpload from './ImgUpload.vue';
|
||||||
|
import { toolbar, plugins, simplePlugins, simpleToolbar, menubar } from './tinymce2';
|
||||||
|
import { buildShortUUID } from '/@/utils/uuid';
|
||||||
|
import { bindHandlers } from './helper';
|
||||||
|
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
|
||||||
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
import { isNumber } from '/@/utils/is';
|
||||||
|
import { useLocale } from '/@/locales/useLocale';
|
||||||
|
import { useAppStore } from '/@/store/modules/app';
|
||||||
|
import { uploadFile } from '/@/api/common/api';
|
||||||
|
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||||
|
const tinymceProps = {
|
||||||
|
options: {
|
||||||
|
type: Object as PropType<Partial<RawEditorSettings>>,
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
toolbar: {
|
||||||
|
type: [Array as PropType<string[]>, String],
|
||||||
|
default: toolbar,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: plugins,
|
||||||
|
},
|
||||||
|
menubar: {
|
||||||
|
type: [Object, String],
|
||||||
|
default: menubar,
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [Number, String] as PropType<string | number>,
|
||||||
|
required: false,
|
||||||
|
default: 120,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [Number, String] as PropType<string | number>,
|
||||||
|
required: false,
|
||||||
|
default: 'auto',
|
||||||
|
},
|
||||||
|
showImageUpload: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Tinymce',
|
||||||
|
components: { ImgUpload },
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: tinymceProps,
|
||||||
|
emits: ['change', 'update:modelValue', 'inited', 'init-error'],
|
||||||
|
setup(props, { emit, attrs }) {
|
||||||
|
const editorRef = ref<Nullable<Editor>>(null);
|
||||||
|
const fullscreen = ref(false);
|
||||||
|
const tinymceId = ref<string>(buildShortUUID('tiny-vue'));
|
||||||
|
const elRef = ref<Nullable<HTMLElement>>(null);
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('tinymce-container');
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const tinymceContent = computed(() => props.modelValue);
|
||||||
|
|
||||||
|
const containerWidth = computed(() => {
|
||||||
|
const width = props.width;
|
||||||
|
if (isNumber(width)) {
|
||||||
|
return `${width}px`;
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
});
|
||||||
|
|
||||||
|
const skinName = computed(() => {
|
||||||
|
return appStore.getDarkMode === 'light' ? 'jeecg' : 'oxide-dark';
|
||||||
|
});
|
||||||
|
|
||||||
|
const langName = computed(() => {
|
||||||
|
const lang = useLocale().getLocale.value;
|
||||||
|
return ['zh_CN', 'en'].includes(lang) ? lang : 'zh_CN';
|
||||||
|
});
|
||||||
|
|
||||||
|
const initOptions = computed((): RawEditorSettings => {
|
||||||
|
const { height, options, toolbar, plugins, menubar } = props;
|
||||||
|
const publicPath = import.meta.env.VITE_PUBLIC_PATH || '/';
|
||||||
|
return {
|
||||||
|
selector: `#${unref(tinymceId)}`,
|
||||||
|
height,
|
||||||
|
toolbar,
|
||||||
|
menubar: menubar,
|
||||||
|
plugins,
|
||||||
|
language_url: publicPath + 'resource/tinymce/langs/' + langName.value + '.js',
|
||||||
|
language: langName.value,
|
||||||
|
branding: false,
|
||||||
|
default_link_target: '_blank',
|
||||||
|
link_title: false,
|
||||||
|
object_resizing: true,
|
||||||
|
toolbar_mode: 'sliding',
|
||||||
|
auto_focus: true,
|
||||||
|
toolbar_groups: true,
|
||||||
|
skin: skinName.value,
|
||||||
|
skin_url: publicPath + 'resource/tinymce/skins/ui/' + skinName.value,
|
||||||
|
images_upload_handler: (blobInfo, success) => {
|
||||||
|
let params = {
|
||||||
|
file: blobInfo.blob(),
|
||||||
|
filename: blobInfo.filename(),
|
||||||
|
data: { biz: 'jeditor', jeditor: '1' },
|
||||||
|
};
|
||||||
|
const uploadSuccess = (res) => {
|
||||||
|
if (res.success) {
|
||||||
|
if (res.message == 'local') {
|
||||||
|
const img = 'data:image/jpeg;base64,' + blobInfo.base64();
|
||||||
|
success(img);
|
||||||
|
} else {
|
||||||
|
let img = getFileAccessHttpUrl(res.message);
|
||||||
|
success(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
uploadFile(params, uploadSuccess);
|
||||||
|
},
|
||||||
|
content_css: publicPath + 'resource/tinymce/skins/ui/' + skinName.value + '/content.min.css',
|
||||||
|
...options,
|
||||||
|
setup: (editor: Editor) => {
|
||||||
|
editorRef.value = editor;
|
||||||
|
editor.on('init', (e) => initSetup(e));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const disabled = computed(() => {
|
||||||
|
const { options } = props;
|
||||||
|
const getdDisabled = options && Reflect.get(options, 'readonly');
|
||||||
|
const editor = unref(editorRef);
|
||||||
|
// update-begin-author:taoyan date:20220407 for: 设置disabled,图片上传没有被禁用
|
||||||
|
if (editor) {
|
||||||
|
editor.setMode(getdDisabled || attrs.disabled === true ? 'readonly' : 'design');
|
||||||
|
}
|
||||||
|
if (attrs.disabled === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// update-end-author:taoyan date:20220407 for: 设置disabled,图片上传没有被禁用
|
||||||
|
return getdDisabled ?? false;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => attrs.disabled,
|
||||||
|
() => {
|
||||||
|
const editor = unref(editorRef);
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editor.setMode(attrs.disabled ? 'readonly' : 'design');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMountedOrActivated(() => {
|
||||||
|
if (!initOptions.value.inline) {
|
||||||
|
tinymceId.value = buildShortUUID('tiny-vue');
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
initEditor();
|
||||||
|
}, 30);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
destory();
|
||||||
|
});
|
||||||
|
|
||||||
|
onDeactivated(() => {
|
||||||
|
destory();
|
||||||
|
});
|
||||||
|
|
||||||
|
function destory() {
|
||||||
|
if (tinymce !== null) {
|
||||||
|
tinymce?.remove?.(unref(initOptions).selector!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initEditor() {
|
||||||
|
const el = unref(elRef);
|
||||||
|
if (el) {
|
||||||
|
el.style.visibility = '';
|
||||||
|
}
|
||||||
|
tinymce
|
||||||
|
.init(unref(initOptions))
|
||||||
|
.then((editor) => {
|
||||||
|
emit('inited', editor);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
emit('init-error', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSetup(e) {
|
||||||
|
const editor = unref(editorRef);
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const value = props.modelValue || '';
|
||||||
|
|
||||||
|
editor.setContent(value);
|
||||||
|
bindModelHandlers(editor);
|
||||||
|
bindHandlers(e, attrs, unref(editorRef));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue(editor: Recordable, val: string, prevVal?: string) {
|
||||||
|
if (editor && typeof val === 'string' && val !== prevVal && val !== editor.getContent({ format: attrs.outputFormat })) {
|
||||||
|
editor.setContent(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindModelHandlers(editor: any) {
|
||||||
|
const modelEvents = attrs.modelEvents ? attrs.modelEvents : null;
|
||||||
|
const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val: string, prevVal: string) => {
|
||||||
|
setValue(editor, val, prevVal);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(val: string, prevVal: string) => {
|
||||||
|
setValue(editor, val, prevVal);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {
|
||||||
|
const content = editor.getContent({ format: attrs.outputFormat });
|
||||||
|
emit('update:modelValue', content);
|
||||||
|
emit('change', content);
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.on('FullscreenStateChanged', (e) => {
|
||||||
|
fullscreen.value = e.state;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleImageUploading(name: string) {
|
||||||
|
const editor = unref(editorRef);
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editor.execCommand('mceInsertContent', false, getUploadingImgName(name));
|
||||||
|
const content = editor?.getContent() ?? '';
|
||||||
|
setValue(editor, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDone(name: string, url: string) {
|
||||||
|
const editor = unref(editorRef);
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const content = editor?.getContent() ?? '';
|
||||||
|
const val = content?.replace(getUploadingImgName(name), `<img src="${url}"/>`) ?? '';
|
||||||
|
setValue(editor, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUploadingImgName(name: string) {
|
||||||
|
return `[uploading:${name}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
prefixCls,
|
||||||
|
containerWidth,
|
||||||
|
initOptions,
|
||||||
|
tinymceContent,
|
||||||
|
elRef,
|
||||||
|
tinymceId,
|
||||||
|
handleImageUploading,
|
||||||
|
handleDone,
|
||||||
|
editorRef,
|
||||||
|
fullscreen,
|
||||||
|
disabled,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
@prefix-cls: ~'@{namespace}-tinymce-container';
|
||||||
|
|
||||||
|
.@{prefix-cls} {
|
||||||
|
position: relative;
|
||||||
|
line-height: normal;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
z-index: -1;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.jeecg-tinymce-img-upload[data-v-aeb4161f] {
|
||||||
|
position: absolute;
|
||||||
|
top: -32px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Any plugins you want to setting has to be imported
|
||||||
|
// Detail plugins list see https://www.tinymce.com/docs/plugins/
|
||||||
|
// Custom builds see https://www.tinymce.com/download/custom-builds/
|
||||||
|
// colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration
|
||||||
|
|
||||||
|
// export const plugins = [
|
||||||
|
// 'advlist anchor autolink autosave code codesample directionality fullscreen hr insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus template textpattern visualblocks visualchars wordcount image',
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// export const toolbar =
|
||||||
|
// 'fullscreen code preview | undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent lineheight|subscript superscript blockquote| numlist bullist checklist | forecolor backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | insertfile image media pageembed link anchor codesample insertdatetime hr| a11ycheck ltr rtl';
|
||||||
|
|
||||||
|
// export const simplePlugins = ['lists image link media table textcolor wordcount contextmenu fullscreen'];
|
||||||
|
|
||||||
|
// export const simpleToolbar = [
|
||||||
|
// 'undo redo formatselect bold italic alignleft aligncenter alignright alignjustify bullist numlist outdent indent',
|
||||||
|
// 'lists link unlink image media table removeformat fullscreen',
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// export const menubar = 'file edit insert view format table';
|
||||||
|
|
||||||
|
export const plugins = [
|
||||||
|
'image',
|
||||||
|
];
|
||||||
|
|
||||||
|
// export const toolbar =
|
||||||
|
// 'fullscreen code preview | undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent lineheight|subscript superscript blockquote| numlist bullist checklist | forecolor backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | insertfile image media pageembed link anchor codesample insertdatetime hr| a11ycheck ltr rtl';
|
||||||
|
|
||||||
|
export const toolbar =
|
||||||
|
'';
|
||||||
|
// fullscreen code preview | undo redo |
|
||||||
|
|
||||||
|
export const simplePlugins = ['lists image link media table textcolor wordcount contextmenu fullscreen'];
|
||||||
|
|
||||||
|
export const simpleToolbar = [
|
||||||
|
'lists link unlink image media table removeformat fullscreen',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const menubar = '';
|
|
@ -172,11 +172,11 @@
|
||||||
>填空</a-button
|
>填空</a-button
|
||||||
></p
|
></p
|
||||||
>
|
>
|
||||||
<!-- <p
|
<p
|
||||||
><a-button type="primary" preIcon="ant-design:check-square-outlined" @click="addTigan(8)" :disabled="editDisabled"
|
><a-button type="primary" preIcon="ant-design:check-square-outlined" @click="addTigan(8)" :disabled="editDisabled"
|
||||||
>文件</a-button
|
>文件</a-button
|
||||||
></p
|
></p
|
||||||
> -->
|
>
|
||||||
</a-card>
|
</a-card>
|
||||||
<a-card title="引用题库">
|
<a-card title="引用题库">
|
||||||
<p><a-button type="primary" @click="handleYylx('0')" :disabled="editDisabled">我的题库</a-button></p>
|
<p><a-button type="primary" @click="handleYylx('0')" :disabled="editDisabled">我的题库</a-button></p>
|
||||||
|
@ -430,14 +430,29 @@
|
||||||
<template #title>
|
<template #title>
|
||||||
<span>{{ index + 1 }}、</span>
|
<span>{{ index + 1 }}、</span>
|
||||||
<a-textarea
|
<a-textarea
|
||||||
placeholder="请填写文件上传题题干"
|
placeholder="请填写文件题题干"
|
||||||
v-model:value="item.wjTitle"
|
v-model:value="item.wjTitle"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:style="{ width: '30rem' }"
|
:style="{ width: '30rem' }"
|
||||||
:auto-size="{ minRows: 1, maxRows: 5 }"
|
:auto-size="{ minRows: 1, maxRows: 5 }"
|
||||||
:disabled="editDisabled"
|
:disabled="editDisabled"
|
||||||
/>
|
/>
|
||||||
<span style="color: #c2bfbf">[文件上传]</span>
|
<span style="color: #c2bfbf">[文件]</span>
|
||||||
|
<!-- <a-textarea
|
||||||
|
placeholder="请填写文件上传题题干"
|
||||||
|
v-model:value="item.wjTitle"
|
||||||
|
:bordered="false"
|
||||||
|
:style="{ width: '30rem' }"
|
||||||
|
:auto-size="{ minRows: 1, maxRows: 5 }"
|
||||||
|
:disabled="editDisabled"
|
||||||
|
/> -->
|
||||||
|
<!-- <JEditor2 placeholder="请填写文件上传题题干"
|
||||||
|
v-model:value="item.wjTitle"
|
||||||
|
:bordered="false"
|
||||||
|
:style="{ width: '30rem' }"
|
||||||
|
:auto-size="{ minRows: 1, maxRows: 5 }"
|
||||||
|
:disabled="editDisabled" v-if="!editDisabled"/>
|
||||||
|
<div v-else v-html="item.wjTitle"></div> -->
|
||||||
</template>
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a-tooltip placement="topLeft" title="题目分数" v-if="isShow">
|
<a-tooltip placement="topLeft" title="题目分数" v-if="isShow">
|
||||||
|
@ -474,7 +489,9 @@
|
||||||
@click="handleDelTigan(item, index)"
|
@click="handleDelTigan(item, index)"
|
||||||
v-if="!editDisabled"
|
v-if="!editDisabled"
|
||||||
/></a-tooltip>
|
/></a-tooltip>
|
||||||
|
<!-- <JUpload ref="uploadRef" /> -->
|
||||||
</template>
|
</template>
|
||||||
|
<j-upload v-model:value="item.picPath" fileType="image" maxCount="9"></j-upload>
|
||||||
</a-card>
|
</a-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -945,10 +962,12 @@ import { list, deleteOne, saveOrUpdate } from '/@/views/kc/wjxWjxx/WjxWjxx.api';
|
||||||
import { Form } from 'ant-design-vue';
|
import { Form } from 'ant-design-vue';
|
||||||
import { getValueType } from '/@/utils';
|
import { getValueType } from '/@/utils';
|
||||||
import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue';
|
import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue';
|
||||||
|
import JEditor2 from '/@/components/Form/src/jeecg/components/JEditor2.vue';
|
||||||
import { useUserStore } from '/@/store/modules/user';
|
import { useUserStore } from '/@/store/modules/user';
|
||||||
import draggable from 'vuedraggable';
|
import draggable from 'vuedraggable';
|
||||||
import { QrCode } from '/@/components/Qrcode/index';
|
import { QrCode } from '/@/components/Qrcode/index';
|
||||||
import { useGlobSetting } from '/@/hooks/setting';
|
import { useGlobSetting } from '/@/hooks/setting';
|
||||||
|
import JUpload from '/@/components/Form/src/jeecg/components/JUpload/JUpload.vue';
|
||||||
|
|
||||||
import WjxWjxxTmlbDjjgsModal from '/@/views/kc/wjxWjxx/components/WjxWjxxTmlbDjjgsModal.vue';
|
import WjxWjxxTmlbDjjgsModal from '/@/views/kc/wjxWjxx/components/WjxWjxxTmlbDjjgsModal.vue';
|
||||||
import XxhbbksListModal from '/@/views/kc/xxhbbks/XxhbbksListModal.vue';
|
import XxhbbksListModal from '/@/views/kc/xxhbbks/XxhbbksListModal.vue';
|
||||||
|
@ -2279,4 +2298,8 @@ onMounted(() => {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: clip;
|
text-overflow: clip;
|
||||||
}
|
}
|
||||||
|
// /deep/.jeecg-tinymce-container{
|
||||||
|
// width: 20rem !important;
|
||||||
|
// height: 100px;
|
||||||
|
// }
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -934,7 +934,7 @@ function handleScoreFabu(record: Recordable) {
|
||||||
}
|
}
|
||||||
//教师批阅作业
|
//教师批阅作业
|
||||||
function handlepiyue(record: Recordable) {
|
function handlepiyue(record: Recordable) {
|
||||||
registerPiyueModal.value.disableSubmit = false;
|
registerPiyueModal.value.disableSubmit = true;
|
||||||
registerPiyueModal.value.edit(record);
|
registerPiyueModal.value.edit(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,32 @@
|
||||||
<a-spin :spinning="confirmLoading">
|
<a-spin :spinning="confirmLoading">
|
||||||
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||||
<a-row>
|
<a-row>
|
||||||
<!-- <a-col :span="24">
|
<a-col :span="12">
|
||||||
<span style="margin-left: 8%;line-height:60px;">预设作业分值:{{zyInfo.score?zyInfo.score+"分 评分不能高于预设分值":'未设置 评分不能高于100分'}}</span>
|
<div v-if="showType=='1'">
|
||||||
</a-col> -->
|
<iframe id="pdfPreviewIframe" :src="ylurl" frameborder="0" width="100%" height="550px" scrolling="auto"></iframe>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="showType=='2'">
|
||||||
|
<img :src="ylurl" style="width: 100%;min-height:200px;" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="showType=='3'">
|
||||||
|
<div class="video-container">
|
||||||
|
<video
|
||||||
|
ref="videoPlayer"
|
||||||
|
:controls="controls"
|
||||||
|
:autoplay="autoplay"
|
||||||
|
:loop="loop"
|
||||||
|
:src="videoUrl"
|
||||||
|
@loadedmetadata="playVideoInFullscreen"
|
||||||
|
>
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<a-button type="primary">下载文件</a-button>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-row>
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<a-form-item label="作业名称">
|
<a-form-item label="作业名称">
|
||||||
{{zyInfo.title}}
|
{{zyInfo.title}}
|
||||||
|
@ -15,6 +38,11 @@
|
||||||
{{formData.studentName}}
|
{{formData.studentName}}
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="评分" v-bind="validateInfos.score">
|
||||||
|
<a-input-number v-model:value="formData.score" style="width: 100%" placeholder="请填写评分" ></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<a-form-item label="批阅内容" v-bind="validateInfos.pyContent">
|
<a-form-item label="批阅内容" v-bind="validateInfos.pyContent">
|
||||||
<JEditor v-model:value="formData.pyContent" />
|
<JEditor v-model:value="formData.pyContent" />
|
||||||
|
@ -22,16 +50,25 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<a-form-item label="批阅附件" v-bind="validateInfos.pyFilePath">
|
<a-form-item label="批阅附件" v-bind="validateInfos.pyFilePath">
|
||||||
<j-upload v-model:value="formData.pyFilePath" :disabled="disabled" maxCount="1" :text="`上传批阅附件`" style="background: #ededed; " :forceAcceptVerify="true" ></j-upload>
|
<j-upload v-model:value="formData.pyFilePath" maxCount="1" :text="`上传批阅附件`" style="background: #ededed; " :forceAcceptVerify="true" ></j-upload>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
<a-col :span="24" style="text-align: center;" v-if="disabled">
|
||||||
|
<div >
|
||||||
|
<a-button type="primary" @click="submitForm">保存</a-button>
|
||||||
|
<a-button type="primary" @click="submitNextForm" style="margin-left: 10px;">保存并下一个</a-button>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted } from 'vue';
|
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted,onUnmounted } from 'vue';
|
||||||
import { defHttp } from '/@/utils/http/axios';
|
import { defHttp } from '/@/utils/http/axios';
|
||||||
import { useMessage } from '/@/hooks/web/useMessage';
|
import { useMessage } from '/@/hooks/web/useMessage';
|
||||||
import JUpload from '/@/components/Form/src/jeecg/components/JUpload/JUpload.vue';
|
import JUpload from '/@/components/Form/src/jeecg/components/JUpload/JUpload.vue';
|
||||||
|
@ -39,6 +76,9 @@
|
||||||
import { saveOrUpdate } from '../ZyInfoStudent.api';
|
import { saveOrUpdate } from '../ZyInfoStudent.api';
|
||||||
import { Form } from 'ant-design-vue';
|
import { Form } from 'ant-design-vue';
|
||||||
import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue';
|
import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue';
|
||||||
|
import { useGlobSetting } from '/@/hooks/setting';
|
||||||
|
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||||
|
import { getToken } from '/@/utils/auth';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
formDisabled: { type: Boolean, default: false },
|
formDisabled: { type: Boolean, default: false },
|
||||||
|
@ -47,18 +87,33 @@
|
||||||
});
|
});
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
const useForm = Form.useForm;
|
const useForm = Form.useForm;
|
||||||
const emit = defineEmits(['register', 'ok']);
|
const emit = defineEmits(['register', 'ok', 'ok2']);
|
||||||
const formData = reactive<Record<string, any>>({
|
const formData = reactive<Record<string, any>>({
|
||||||
id: '',
|
id: '',
|
||||||
score: '',
|
score: '',
|
||||||
|
pyContent: '',
|
||||||
|
pyFilePath: '',
|
||||||
});
|
});
|
||||||
const { createMessage } = useMessage();
|
const { createMessage } = useMessage();
|
||||||
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
|
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
|
||||||
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
|
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
|
||||||
const confirmLoading = ref<boolean>(false);
|
const confirmLoading = ref<boolean>(false);
|
||||||
|
const showType = ref<string>('0');
|
||||||
|
const ylurl = ref<string>('');
|
||||||
const zyInfo = ref<any>({});
|
const zyInfo = ref<any>({});
|
||||||
|
const globSetting = useGlobSetting();
|
||||||
|
const baseApiUrl = globSetting.domainUrl;
|
||||||
|
|
||||||
|
const videoPlayer = ref(null);
|
||||||
|
const videoOpen = ref<boolean>(false);
|
||||||
|
const controls = ref(true);
|
||||||
|
const autoplay = ref(false)
|
||||||
|
const loop = ref(false);
|
||||||
|
const videoUrl = ref<String>('');
|
||||||
|
|
||||||
//表单验证
|
//表单验证
|
||||||
const validatorRules = {
|
const validatorRules = {
|
||||||
|
score: [{ required: true, message: '请输入分数!' }],
|
||||||
};
|
};
|
||||||
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: true });
|
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: true });
|
||||||
|
|
||||||
|
@ -111,6 +166,7 @@
|
||||||
* 编辑
|
* 编辑
|
||||||
*/
|
*/
|
||||||
function edit(record) {
|
function edit(record) {
|
||||||
|
formData.pyContent = ''
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
resetFields();
|
resetFields();
|
||||||
|
|
||||||
|
@ -118,14 +174,90 @@
|
||||||
console.log(`🚀 ~ defHttp.get ~ res:`, res)
|
console.log(`🚀 ~ defHttp.get ~ res:`, res)
|
||||||
zyInfo.value = res;
|
zyInfo.value = res;
|
||||||
})
|
})
|
||||||
|
const parts = record.filePath.split('.');
|
||||||
|
const filetype = parts[parts.length - 1];
|
||||||
|
console.log(`🚀 ~ nextTick ~ filetype:`, filetype)
|
||||||
|
var file = getFileAccessHttpUrl(record.filePath);
|
||||||
|
ylurl.value = file;
|
||||||
|
if(filetype=='jpg' || filetype=='png' || filetype=='jpeg' || filetype=='xls' || filetype=='xlsx' || filetype=='text' ){
|
||||||
|
showType.value = '2';
|
||||||
|
}else if(filetype=='doc' || filetype=='docx' || filetype=='pdf'){
|
||||||
|
showType.value = '1';
|
||||||
|
var file2 = getFileAccessHttpUrl(record.pdfPath);
|
||||||
|
let url2 = baseApiUrl + '/generic/web/viewer.html?file=' + encodeURIComponent(file2);
|
||||||
|
ylurl.value = url2;
|
||||||
|
}else if(filetype=='mp4'|| filetype=='avi'|| filetype=='mp3'|| filetype=='wav'){
|
||||||
|
showType.value = '3';
|
||||||
|
|
||||||
|
let url = getFileAccessHttpUrl(record.filePath);
|
||||||
|
console.log('视频预览-----》',url);
|
||||||
|
// videoOpen.value = true;
|
||||||
|
videoUrl.value = url;
|
||||||
|
|
||||||
|
|
||||||
|
}else{
|
||||||
|
showType.value = '0';
|
||||||
|
}
|
||||||
//赋值
|
//赋值
|
||||||
Object.assign(formData, record);
|
Object.assign(formData, record);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------视频------------------------------
|
||||||
|
const playVideo = () => {
|
||||||
|
videoPlayer.value.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
const pauseVideo = () => {
|
||||||
|
videoPlayer.value.pause();
|
||||||
|
};
|
||||||
|
const handleFullScreenChange = () => {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
console.log('Video exited fullscreen');
|
||||||
|
// 这里可以执行取消全屏后的逻辑
|
||||||
|
videoOpen.value = false;
|
||||||
|
videoPlayer.value.pause();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function videoHandleCancel(){
|
||||||
|
pauseVideo();
|
||||||
|
videoOpen.value = false;
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('fullscreenchange', handleFullScreenChange);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('fullscreenchange', handleFullScreenChange);
|
||||||
|
});
|
||||||
|
|
||||||
|
//视频预览
|
||||||
|
function handleVideo(three){
|
||||||
|
let url = getFileAccessHttpUrl(three.filePath);
|
||||||
|
console.log('视频预览-----》',url);
|
||||||
|
videoOpen.value = true;
|
||||||
|
videoUrl.value = url;
|
||||||
|
playVideoInFullscreen();
|
||||||
|
// setTimeout(() => {
|
||||||
|
// playVideo();
|
||||||
|
// }, 1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------视频------------------------------
|
||||||
|
const playVideoInFullscreen = async () => {
|
||||||
|
if (videoPlayer.value) {
|
||||||
|
try {
|
||||||
|
// 使用原生DOM方法请求全屏
|
||||||
|
// await videoPlayer.value.requestFullscreen();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交数据
|
* 提交数据
|
||||||
*/
|
*/
|
||||||
|
@ -171,6 +303,49 @@
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交数据并下一个
|
||||||
|
*/
|
||||||
|
async function submitNextForm() {
|
||||||
|
// 触发表单验证
|
||||||
|
await validate();
|
||||||
|
confirmLoading.value = true;
|
||||||
|
const isUpdate = ref<boolean>(false);
|
||||||
|
//时间格式化
|
||||||
|
let model = formData;
|
||||||
|
if (model.id) {
|
||||||
|
isUpdate.value = true;
|
||||||
|
}
|
||||||
|
//循环数据
|
||||||
|
for (let data in model) {
|
||||||
|
//如果该数据是数组并且是字符串类型
|
||||||
|
if (model[data] instanceof Array) {
|
||||||
|
let valueType = getValueType(formRef.value.getProps, data);
|
||||||
|
//如果是字符串类型的需要变成以逗号分割的字符串
|
||||||
|
if (valueType === 'string') {
|
||||||
|
model[data] = model[data].join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await defHttp.post({url:'/zyInfoStudent/zyInfoStudent/editPiyue',params:model}).then(res =>{
|
||||||
|
emit('ok2');
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
await defHttp.get({url:'/zyInfoStudent/zyInfoStudent/getList2',params:{column:'createTime',order:'asc',pageNo:1,pageSize:10,ywid:zyInfo.value.id,rwbh:zyInfo.value.rwbh,queryType:5}}).then(res =>{
|
||||||
|
console.log(`🚀 ~ awaitdefHttp.post ~ res:`, res)
|
||||||
|
var list = res.records;
|
||||||
|
if(list.length>0){
|
||||||
|
edit(list[0])
|
||||||
|
}else{
|
||||||
|
createMessage.warning('已批阅完毕,暂无下一条批阅数据!');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
add,
|
add,
|
||||||
|
@ -185,4 +360,11 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 24px 24px 24px 24px;
|
padding: 24px 24px 24px 24px;
|
||||||
}
|
}
|
||||||
|
.video-container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
video {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%; /* 确保视频宽度不超过其容器宽度 */
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<a-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
|
<a-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
|
||||||
<ZyInfoStudentPiyueForm ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></ZyInfoStudentPiyueForm>
|
<ZyInfoStudentPiyueForm ref="registerForm" @ok="submitCallback" @ok2="submitCallback2" :formDisabled="disableSubmit" :formBpm="false"></ZyInfoStudentPiyueForm>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
import ZyInfoStudentPiyueForm from './ZyInfoStudentPiyueForm.vue'
|
import ZyInfoStudentPiyueForm from './ZyInfoStudentPiyueForm.vue'
|
||||||
|
|
||||||
const title = ref<string>('');
|
const title = ref<string>('');
|
||||||
const width = ref<number>(800);
|
const width = ref<string>('100%');
|
||||||
const visible = ref<boolean>(false);
|
const visible = ref<boolean>(false);
|
||||||
const disableSubmit = ref<boolean>(false);
|
const disableSubmit = ref<boolean>(false);
|
||||||
const registerForm = ref();
|
const registerForm = ref();
|
||||||
|
@ -53,6 +53,10 @@
|
||||||
emit('success');
|
emit('success');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function submitCallback2() {
|
||||||
|
emit('success');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消按钮回调事件
|
* 取消按钮回调事件
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue