pptList 功能二开

This commit is contained in:
zdg 2024-11-22 15:18:42 +08:00
parent 82091fbef5
commit 8707b54420
8 changed files with 520 additions and 15 deletions

View File

@ -8,7 +8,7 @@
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
/> -->
<meta http-equiv="Content-Security-Policy" content="connect-src *; default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *;img-src * 'self' data: blob:" />
<meta http-equiv="Content-Security-Policy" content="connect-src *; default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src * blob:;img-src * 'self' data: blob:" />
</head>

View File

@ -0,0 +1,65 @@
<template>
<div id="aptContainer"></div>
</template>
<script>
export default {
props: {
modelValue:[Object,String], // json
width: { //
type: Number,
default: 0
},
height: { //
type: Number,
default: 0
},
domId: { // ID
type: String,
default: 'aptContainer'
}
},
data() {
return {
description: 'apt预览组件-konva',
// konva
konvaStage:null, konvaLayer:null
}
},
mounted() { //
this.initKonva()
},
watch: {
modelValue: {
immediate: true, //
handler(v) { // 12
!!v && this.loadJson()
}
}
},
methods: {
// Konva
initKonva() {
if(!this.width || !this.height) return false // 使
this.konvaStage = new Konva.Stage({
container: "aptContainer", // id of container <div>
width: this.width,
height: this.height,
});
},
//
loadJson(data = this.modelValue) {
if (!data) return false
this.konvaStage = Konva.Node.create(data, this.domId)
}
}
}
</script>
<style lang="scss" scoped>
.aptContainer{
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<el-link :href="href" target="_blank" type="primary">
<slot name="content">{{content}}</slot>
</el-link>
</template>
<script>
export default {
props: {
href: { //
type: String,
default: 'https://beian.miit.gov.cn'
}
},
data() {
return {
description: '备案号-添加',
content: '备案号苏ICP备2024097972号-1'
}
},
methods: {
}
}
</script>
<style lang="less" scoped>
</style>

View File

@ -1,6 +1,5 @@
<template>
<!-- 表单-组件(自定义) -->
<slot>
<el-row v-bind="rows||{}">
<!-- 其他内容-start -->
<slot name="start"></slot>
@ -80,7 +79,6 @@
<!-- 其他内容-end -->
<slot name="end"></slot>
</el-row>
</slot>
</template>
<script>
export default {
@ -161,7 +159,9 @@ export default {
// --
confirm() {
const formRefs = this.$refs.form
const isBool = this.$attrs.confirm && typeof this.$attrs.confirm === 'function'
this.$emit('confirm', formRefs)
if (isBool) this.$attrs.confirm(formRefs)
},
//
resetFields() {

View File

@ -0,0 +1,44 @@
<template>
<el-pagination v-bind="pageOpt"
@change="(...agrs) => $emit('pageChange',...agrs)"
v-model:current-page="curPage" v-model:page-size="limit" />
</template>
<script>
// -
export default {
name: 'cPage',
props: {
page: { type: Object, required: true },
},
data() {
return {
def: {
// size: 'small',
background: false,
pageSizes: [10, 20, 30, 40, 50, 100],
layout: 'total, sizes, prev, pager, next',
// layout: 'total, sizes, prev, pager, next, jumper',
}
}
},
computed: {
pageOpt() {
return Object.assign({},this.def, this.page)
},
curPage: {
get() { return this.page.curPage },
set(val) { this.page.curPage = val }
},
limit: {
get() { return this.page.limit },
set(val) { this.page.limit = val }
},
},
created() {},
methods: {}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,280 @@
<template>
<!-- 组件 -->
<div>
<slot name="top">
<div class="c-search" :hide="!isFold||isDef" v-if="fItems.length" :style="$attrs.style">
<transition name="el-zoom-in-top">
<c-form v-show="isFold" ref="search" v-if="!noSearch && search" :form="search"
:itemOption="fItems" :option="fOption" :onEnter="fOnEnter" :rows="fRows" :cols="fCols">
<template v-for="item in formSlots" #[item]="data">
<slot :name="`f${item}`" v-bind="data"></slot>
</template>
<template #append>
<slot name="topAdd" v-if="$slots.topAdd"></slot>
<slot name="search" v-if="!$attrs.searchBtnNe">
<el-col style="text-align: center;margin: 5px 0 15px;">
<el-button icon="Search" type="primary" @click="$emit('change','query')">查询</el-button>
<el-button type="warning" @click="$emit('change','reset')">重置</el-button>
</el-col>
</slot>
</template>
</c-form>
</transition>
<div v-if="!isDef" :class="['fold',{hide:!isFold}]" @click="isFold=!isFold">
<!-- <i :class="'el-icon-arrow-' + (isFold?'down':'up')"></i> -->
<el-icon><component :is="isFold?'ArrowUp':'ArrowDown'"></component></el-icon>
</div>
</div>
</slot>
<slot name="start"></slot>
<div :class="['m-list',{'ne':!isMain, hide:!isFold}]">
<slot name="tStart"></slot>
<el-table ref="table" v-bind="tAttrs" v-on="tOns">
<template #empty><slot name="empty">{{emptyText}}</slot></template>
<template #append><slot name="append"></slot></template>
<slot>
<template v-for="(item, index) in optionItems" :key="index">
<el-table-column v-bind="item">
<!-- -表头-自定义 -->
<template #header="{column, $index}">
<slot :name="item.prop+'_header'" :column="column" :$index="$index">
<slot name="header" :column="column" :$index="$index">{{ item.label }}</slot>
</slot>
</template>
<!-- 列内容-自定义 -->
<template #default="{row, column, $index}" v-if="item.type!='selection'">
<slot :name="item.prop" :row="row" :column="column" :$index="$index" :value="row[item.prop]">
<span v-if="item.attrs" v-bind="item.attrs">{{ defVal(row,item, $index, $attrs, column) }}</span>
<slot v-else :row="row" :column="column" :$index="$index" :value="row[item.prop]">{{ defVal(row,item, $index, $attrs, column) }}</slot>
</slot>
</template>
</el-table-column>
</template>
</slot>
</el-table>
<slot name="end"></slot>
<slot name="page">
<c-page v-if="!noPage" :page="page" v-on="$attrs" class="c-page" border="false"></c-page>
</slot>
</div>
</div>
</template>
<script>
import cForm from './cForm.vue'
export default {
// inheritAttrs: false,
components: { cForm },
name: 'cTable',
props: {
option: Array, //
search: Object, //
fItems: { //
type: Array,
default: () => []
},
fOption: { // -
type: Object,
default: () => ({ labelW: '80px' })
// default: () => ({ inline: true, labelW: '80px', size: 'small' })
},
fOnEnter: Function, // ---
fRows: { // -
type: Object,
default: _ => ({ gutter: 20 })
},
fCols: { // - inline
type: Object,
default: _ => ({ span: 8, xs: 24, sm: 12, md: 8, lg: 6 })
},
emptyText: {
type: String,
default: '暂无数据'
},
isDef: { // -
type: Boolean,
default: true
},
isMain: { //
type: Boolean,
default: true
},
fold: { //
type: Boolean,
default: true
},
data: Object, //
page: Object, //
noSearch: Boolean, //
noPage: Boolean, //
noDef: Boolean, //
isMaxHeight: Boolean, //
},
data() {
return {
// -
formSlots: [], isFold: this.fold, height: null,
}
},
computed: {
optionItems() { //
const list = this.option || []
return list.map(o => {
const def = { align: 'center', showOverflowTooltip: true }
if (o.width && o.showOverflowTooltip == undefined){
o.showOverflowTooltip = true //
}
return this.noDef ? o : Object.assign(def, o)
})
.filter(o => o.show ?? true) // -show
.filter(o => o.visible ?? true) // -visible
},
tAttrs() { // $attrs
const attrs = {...this.$attrs}
Object.keys(attrs).forEach(key => {
//
if (!attrs[key]??null) delete attrs[key]
//
else if (/^page.*/.test(key)) delete attrs[key]
})
// 'style', 'class', 't-style', 't-class'
const tArr = Object.keys(attrs).filter(k => k.startsWith('t-'))
if (tArr.length) tArr.forEach(k => attrs[k.slice(2)] = attrs[k])
else ['style', 'class'].forEach(k => delete attrs[k])
//
const hArrs = ['height', 'max-height', 'maxHeight']
const isH = Object.keys(attrs).some(k => hArrs.includes(k))
//
if (!isH) attrs[this.isMaxHeight?'max-height':'height'] = 500
//
if (this.data) attrs.data = this.data
return attrs
},
tOns() { // $on
const attrs = {...this.$attrs}
return Object.keys(attrs).reduce((p, c, a ) => {
if(typeof attrs[c]=='function')p[c]=attrs[c]; return p
}, {})
}
},
created() {},
mounted() {
this.getFormSlots()
},
methods: {
// --
getFormSlots() {
const props = this.fItems.map(o => o.prop)
const keys = Object.keys(this.$slots)
this.formSlots = keys.filter(k => {
const isForm = /^f.*/.test(k) //
const isProp = props.some(p => k.endsWith(p)) // -
return isForm && isProp
}).map(k => k.replace(/^f/, '')) //
},
//
defVal(row, item, rowInd, attr, column) {
switch (item.type) {
case 'index': { // -ind
const { curPage, limit, offset } = attr.page || attr
const start = offset || (curPage - 1) * limit || 0
return rowInd + 1 + start
}
default:
return item.format ? item.format(row[item.prop], row, column, rowInd) : row[item.prop]
}
}
},
}
</script>
<style lang="scss" scoped>
// @import '~@/assets/styles/cmixin';
// $w | $h | $bg | $tBr $tBg
@mixin scrollBar($w:8px,$h:8px,$tBr:20px,
$bg:#d3dce6,$tBg:#99a9bf, $tHbg: #409eff) {
&::-webkit-scrollbar-track-piece {
background: $bg;
}
&::-webkit-scrollbar {
width: $w;
height: $h;
}
&::-webkit-scrollbar-thumb {
background: $tBg;
border-radius: $tBr;
@if $tHbg {
&:hover{
background: $tHbg;
}
}
}
}
:deep(.el-table){
.el-table__expand-column{
overflow: hidden;
}
.el-table__body-wrapper{
@include scrollBar;
}
}
// css
.c-search{
--bg: #fff;
padding-top: 10px;
// padding: 20px 20px 0;
background: var(--bg);
margin-bottom: 30px;
border-radius: 4px;
&[hide=true]{
margin: 0;
padding: 0;
}
.fold{
--box-color: rgba(24,144,255,0.2);
--box-hover-color: rgba(24,144,255,0.6);
width: 100px;
height: 20px;
cursor: pointer;
display: inline-block;
text-align: center;
position: absolute;
background: var(--bg);
border-radius: 0 0 50px 50px;
left: 50%;
transform: translate(-50%,5px);
box-shadow: 0px 3px 3px var(--box-color);
i{font-size: inherit;}
&:hover{
box-shadow: 0px 3px 3px var(--box-hover-color);
i{color: #1890ff;}
}
&.hide{
top: 12px;
border-radius: 50px 50px 0 0;
box-shadow: 0 -3px 3px var(--box-color);
&:hover{box-shadow: 0 -3px 3px var(--box-hover-color);}
}
}
:deep(.el-form){
.el-form-item{
margin-bottom: 10px;
}
}
}
//
.m-list{
padding: 20px;
border-radius: 4px;
&.ne{padding: 0;}
&.hide{
margin-top: 20px;
}
}
.c-page{
background: #fff;
margin-top: 10px;
justify-content: flex-end;
}
</style>

View File

@ -1,23 +1,75 @@
<template>
<div>
<div class="mb-4">
<el-button type="primary" @click="onchange('/model/curriculum')">课标研读</el-button>
<el-button type="primary" @click="onchange('/model/management')">作业管理</el-button>
<el-button type="success" @click="onchange('/model/teaching')">教材研读</el-button>
<el-button type="success" @click="openPPTist">打开PPTist</el-button>
<!-- <el-button type="info" @click="onchange('/model/examination')">考试分析</el-button> -->
</div>
<ChooseTextbook @change-book="getData" @node-click="getData" />
</div>
<!-- 顶部 按钮功能 -->
<div class="mb-4">
<el-button type="primary" @click="onchange('/model/curriculum')">课标研读</el-button>
<el-button type="primary" @click="onchange('/model/management')">作业管理</el-button>
<el-button type="success" @click="onchange('/model/teaching')">教材研读</el-button>
<el-button type="success" @click="openPPTist">打开PPTist</el-button>
<!-- <el-button type="info" @click="onchange('/model/examination')">考试分析</el-button> -->
</div>
<el-row class="container">
<!-- 左侧 选择教材 目录 -->
<ChooseTextbook @change-book="changeBook" @node-click="changeBook" />
<!-- 中间 展示内容 -->
<el-col :span="10">
<div class="c-item mb-4 mx-4">
<div class="flex justify-between pb-2">
<h3>教师资源</h3>
<span class="c-btns">
<el-button size="small" text :icon="Refresh" @click="handleAll('refresh')">刷新</el-button>
<el-button size="small" text :icon="Files" @click="handleAll('resource')">资源库</el-button>
<el-button size="small" text :icon="UploadFilled" @click="handleAll('upload')">上传</el-button>
<el-button size="small" text :icon="Plus" @click="handleAll('add')">添加</el-button>
</span>
</div>
<c-table ref="resourRef" v-bind="sourceOpt" t-class="rounded"></c-table>
</div>
</el-col>
</el-row>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { Plus, Refresh, Upload, Files, UploadFilled } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
import { createWindow } from '@/utils/tool' //
import * as entpcoursefile from '@/api/education/entpcoursefile' // api
//
import ChooseTextbook from '@/components/choose-textbook/index.vue'
const router = useRouter()
const bookNode = reactive({node:{}, textBook:{}}) //
const dt = reactive({ //
curRow: null, //
})
// ref
const resourRef = ref() // ref
// -cTable
const sourceOpt = reactive({
data: [], //
option: [ //
{ label: '名称', prop: 'title', align: 'left' },
{ label: '类型', prop: 'type' },
{ label: '时间', prop: 'createTime', width: 160, sortable: true },
],
noPage: true, //
isMain: false, //
highlightCurrentRow: true, //
rowClick: (r, c, e) => { //
if (dt.curRow == r) { // -
resourRef.value.$refs.table.setCurrentRow()
dt.curRow = null
} else dt.curRow = r
}
})
sourceOpt.data = [
{ title: '测试学校' },
{ title: '测试学校2' },
{ title: '测试学校3' },
]
// -methods
const openPPTist = () =>{
createWindow('open-win', {url: '/pptist'})
}
@ -25,6 +77,36 @@ const openPPTist = () =>{
const onchange = (path) =>{
router.push(path)
}
//
const changeBook = ({node, textBook}) =>{
console.log(node, textBook)
bookNode.node = node
bookNode.textBook = textBook
}
//
const handleAll = (type) =>{
console.log(type)
switch (type) {
case 'refresh': //
break;
case 'resource': //
break;
case 'upload': //
break;
case 'add':{ //
break;
}
}
}
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.container{
height: calc(100% - 32px - 1rem);
.c-item{
.c-btns{
.el-button{margin: 0;}
}
}
}
</style>

View File

@ -1,4 +1,11 @@
{
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*":["src/renderer/src/*"],
"@root/*":["./*"]
}
},
"files": [],
"references": [
{