maxkb/ui/src/views/paragraph/index.vue
2025-07-14 15:54:15 +08:00

407 lines
13 KiB
Vue
Raw 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.

<template>
<div class="paragraph p-12-24">
<div class="flex align-center" style="width: 78%">
<back-button to="-1" style="margin-left: -4px"></back-button>
<h3 style="display: inline-block">{{ documentDetail?.name }}</h3>
<el-text type="info" v-if="documentDetail?.type === '1'"
>{{ $t('views.document.form.source_url.label') }}<el-link
:href="documentDetail?.meta?.source_url"
target="_blank"
>
<span class="break-all">{{ documentDetail?.meta?.source_url }} </span></el-link
>
</el-text>
</div>
<div class="header-button" v-if="!shareDisabled">
<el-button @click="batchSelectedHandle(true)" v-if="isBatch === false">
{{ $t('views.paragraph.setting.batchSelected') }}
</el-button>
<el-button @click="batchSelectedHandle(false)" v-if="isBatch === true">
{{ $t('views.paragraph.setting.cancelSelected') }}
</el-button>
<el-button @click="addParagraph" type="primary" :disabled="loading" v-if="isBatch === false">
{{ $t('views.paragraph.addParagraph') }}
</el-button>
</div>
<el-card
style="--el-card-padding: 0"
class="paragraph__main mt-16"
v-loading="(paginationConfig.current_page === 1 && loading) || changeStateloading"
>
<div class="flex-between p-12-16 border-b">
<span>{{ paginationConfig.total }} {{ $t('views.paragraph.paragraph_count') }}</span>
<el-input
v-model="search"
:placeholder="$t('common.search')"
class="input-with-select"
style="width: 260px"
@change="searchHandle"
clearable
>
<template #prepend>
<el-select v-model="searchType" placeholder="Select" style="width: 80px">
<el-option :label="$t('common.title')" value="title" />
<el-option :label="$t('common.content')" value="content" />
</el-select>
</template>
</el-input>
</div>
<LayoutContainer showCollapse>
<template #left>
<div class="paragraph-sidebar p-16">
<el-scrollbar class="paragraph-scollbar">
<el-anchor
direction="vertical"
type="default"
:offset="130"
container=".paragraph-scollbar"
@click="handleClick"
>
<template v-for="(item, index) in paragraphDetail" :key="item.id">
<el-anchor-link :href="`#m${item.id}`" :title="item.title" v-if="item.title">
<span :title="item.title">
{{ item.title }}
</span>
</el-anchor-link>
</template>
</el-anchor>
</el-scrollbar>
</div>
</template>
<div class="w-full">
<el-empty v-if="paragraphDetail.length == 0" :description="$t('common.noData')" />
<div v-else>
<el-scrollbar class="paragraph-scollbar">
<div class="paragraph-detail">
<el-checkbox-group v-model="multipleSelection">
<InfiniteScroll
:size="paragraphDetail.length"
:total="paginationConfig.total"
:page_size="paginationConfig.page_size"
v-model:current_page="paginationConfig.current_page"
@load="getParagraphList"
:loading="loading"
>
<VueDraggable
ref="el"
v-model="paragraphDetail"
:disabled="isBatch === true || shareDisabled"
handle=".handle"
:animation="150"
ghostClass="ghost"
@end="onEnd"
>
<template v-for="(item, index) in paragraphDetail" :key="item.id">
<div :id="`m${item.id}`" class="flex mb-16">
<!-- 批量操作 -->
<div class="paragraph-card flex w-full" v-if="isBatch === true">
<el-checkbox :value="item.id" />
<ParagraphCard :data="item" class="mb-8 w-full" :disabled="true" />
</div>
<!-- 非批量操作 -->
<div class="handle paragraph-card flex w-full" :id="item.id" v-else>
<img
src="@/assets/sort.svg"
alt=""
height="15"
class="handle-img mr-8 mt-24 cursor"
/>
<ParagraphCard
:data="item"
:showMoveUp="index !== 0"
:showMoveDown="index < paragraphDetail.length-1"
class="mb-8 w-full"
@changeState="changeState"
@deleteParagraph="deleteParagraph"
@move="(val: string)=>onEnd(null, {paragraph_id: item.id,new_position: val==='up'?index-1:index+1}, index)"
@refresh="refresh"
@refreshMigrateParagraph="refreshMigrateParagraph"
:disabled="shareDisabled"
/>
</div>
</div>
</template>
</VueDraggable>
</InfiniteScroll>
</el-checkbox-group>
</div>
</el-scrollbar>
</div>
</div>
</LayoutContainer>
<div class="mul-operation border-t w-full" v-if="isBatch === true">
<el-button :disabled="multipleSelection.length === 0" @click="openGenerateDialog()">
{{ $t('views.document.generateQuestion.title') }}
</el-button>
<el-button :disabled="multipleSelection.length === 0" @click="openSelectDocumentDialog()">
{{ $t('views.document.setting.migration') }}
</el-button>
<el-button :disabled="multipleSelection.length === 0" @click="deleteMulParagraph">
{{ $t('common.delete') }}
</el-button>
<span class="ml-8">
{{ $t('common.selected') }} {{ multipleSelection.length }}
{{ $t('views.document.items') }}
</span>
</div>
</el-card>
<ParagraphDialog
ref="ParagraphDialogRef"
:title="title"
:apiType="apiType"
@refresh="refresh"
/>
<SelectDocumentDialog
ref="SelectDocumentDialogRef"
@refresh="refreshMigrateParagraph"
:apiType="apiType"
/>
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" @refresh="refresh" :apiType="apiType" />
</div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import ParagraphDialog from './component/ParagraphDialog.vue'
import ParagraphCard from './component/ParagraphCard.vue'
import SelectDocumentDialog from './component/SelectDocumentDialog.vue'
import GenerateRelatedDialog from '@/components/generate-related-dialog/index.vue'
import { VueDraggable } from 'vue-draggable-plus'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
import { t } from '@/locales'
const route = useRoute()
const {
params: { id, documentId },
query: { type, isShared },
} = route as any
const apiType = computed(() => {
return type as 'systemShare' | 'workspace' | 'systemManage'
})
const shareDisabled = computed(() => {
return isShared === 'true'
})
const SelectDocumentDialogRef = ref()
const ParagraphDialogRef = ref()
const loading = ref(false)
const changeStateloading = ref(false)
const documentDetail = ref<any>({})
const paragraphDetail = ref<any[]>([])
const title = ref('')
const search = ref('')
const searchType = ref('title')
const handleClick = (e: MouseEvent, ele: any) => {
e.preventDefault()
document.querySelector(`${ele}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
// 批量操作
const isBatch = ref(false)
const multipleSelection = ref<any[]>([])
const paginationConfig = reactive({
current_page: 1,
page_size: 30,
total: 0,
})
function deleteParagraph(id: string) {
const index = paragraphDetail.value.findIndex((v) => v.id === id)
paragraphDetail.value.splice(index, 1)
}
function changeState(id: string) {
const index = paragraphDetail.value.findIndex((v) => v.id === id)
paragraphDetail.value[index].is_active = !paragraphDetail.value[index].is_active
}
function refreshMigrateParagraph(data: any) {
console.log(data)
if (data) {
multipleSelection.value = [data.id]
}
console.log(paragraphDetail.value)
console.log(multipleSelection.value)
paragraphDetail.value = paragraphDetail.value.filter(
(v) => !multipleSelection.value.includes(v.id),
)
console.log(paragraphDetail.value)
multipleSelection.value = []
MsgSuccess(t('views.document.tip.migrationSuccess'))
}
function openSelectDocumentDialog(row?: any) {
if (row) {
multipleSelection.value = [row.id]
}
SelectDocumentDialogRef.value.open(multipleSelection.value)
}
function deleteMulParagraph() {
MsgConfirm(
`${t('views.document.delete.confirmTitle1')} ${multipleSelection.value.length} ${t('views.document.delete.confirmTitle2')}`,
t('views.paragraph.delete.confirmMessage'),
{
confirmButtonText: t('common.confirm'),
confirmButtonClass: 'danger',
},
)
.then(() => {
loadSharedApi({ type: 'paragraph', systemType: apiType.value })
.putMulParagraph(id, documentId, multipleSelection.value, changeStateloading)
.then(() => {
paragraphDetail.value = paragraphDetail.value.filter(
(v) => !multipleSelection.value.includes(v.id),
)
multipleSelection.value = []
MsgSuccess(t('views.document.delete.successMessage'))
})
})
.catch(() => {})
}
function batchSelectedHandle(bool: boolean) {
isBatch.value = bool
multipleSelection.value = []
}
function searchHandle() {
paginationConfig.current_page = 1
paragraphDetail.value = []
getParagraphList()
}
function addParagraph() {
title.value = t('views.paragraph.addParagraph')
ParagraphDialogRef.value.open()
}
function getDetail() {
loadSharedApi({ type: 'document', isShared: shareDisabled.value, systemType: apiType.value })
.getDocumentDetail(id, documentId, loading)
.then((res: any) => {
documentDetail.value = res.data
})
}
function getParagraphList() {
loadSharedApi({ type: 'paragraph', isShared: shareDisabled.value, systemType: apiType.value })
.getParagraphPage(
id,
documentId,
paginationConfig,
search.value && { [searchType.value]: search.value },
loading,
)
.then((res: any) => {
paragraphDetail.value = [...paragraphDetail.value, ...res.data.records]
paginationConfig.total = res.data.total
})
}
function refresh(data: any) {
if (data) {
const index = paragraphDetail.value.findIndex((v) => v.id === data.id)
paragraphDetail.value.splice(index, 1, data)
} else {
paginationConfig.current_page = 1
paragraphDetail.value = []
getParagraphList()
}
}
const GenerateRelatedDialogRef = ref()
function openGenerateDialog(row?: any) {
const arr: string[] = []
if (row) {
arr.push(row.id)
} else {
multipleSelection.value.map((v) => {
if (v) {
arr.push(v)
}
})
}
GenerateRelatedDialogRef.value.open(arr, 'paragraph')
}
function onEnd(event?: any, params?: any, index?: number) {
const obj =params ?? {
paragraph_id: paragraphDetail.value[event.newIndex].id, // 当前拖动的段落ID
new_position: paragraphDetail.value[event.newIndex + 1].position, // 新位置的段落位置
}
loadSharedApi({ type: 'paragraph', systemType: apiType.value }).putAdjustPosition(
id,
documentId,
obj,
loading,
)
if(params) {
const movedItem = paragraphDetail.value.splice(index as number, 1)[0]
paragraphDetail.value.splice(params.new_position, 0, movedItem)
}
}
onMounted(() => {
getDetail()
getParagraphList()
})
</script>
<style lang="scss" scoped>
.paragraph {
position: relative;
.header-button {
position: absolute;
right: calc(var(--app-base-px) * 3);
top: calc(var(--app-base-px) + 4px);
}
.paragraph-sidebar {
width: 100%;
height: calc(100vh - 215px);
box-sizing: border-box;
}
.paragraph-detail {
height: calc(100vh - 215px);
max-width: 1000px;
margin: 16px auto;
.el-checkbox-group {
font-size: inherit;
line-height: inherit;
}
}
&__main {
position: relative;
box-sizing: border-box;
.mul-operation {
position: absolute;
bottom: 0;
left: 0;
padding: 16px 24px;
box-sizing: border-box;
background: #ffffff;
}
}
.paragraph-card {
&.handle {
.handle-img {
visibility: hidden;
}
&:hover {
.handle-img {
visibility: visible;
}
}
}
}
}
</style>