|
@@ -10,7 +10,9 @@
|
|
|
|
|
|
|
|
<el-form :inline="true" :model="queryParams" class="search-form">
|
|
<el-form :inline="true" :model="queryParams" class="search-form">
|
|
|
<el-form-item label="流程名称">
|
|
<el-form-item label="流程名称">
|
|
|
- <el-input v-model="queryParams.processName" placeholder="请输入流程名称" clearable />
|
|
|
|
|
|
|
+ <el-select v-model="queryParams.processName" placeholder="全部流程" clearable style="width: 200px;">
|
|
|
|
|
+ <el-option v-for="def in definitionList" :key="def.id" :label="def.name" :value="def.name" />
|
|
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
<el-form-item>
|
|
|
<el-button type="primary" @click="handleQuery">查询</el-button>
|
|
<el-button type="primary" @click="handleQuery">查询</el-button>
|
|
@@ -38,74 +40,124 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div v-loading="loading" class="task-card-list">
|
|
|
|
|
- <el-empty v-if="!loading && tableData.length === 0" description="暂无待办任务" />
|
|
|
|
|
- <el-card
|
|
|
|
|
- v-for="row in filteredTableData"
|
|
|
|
|
- :key="row.id"
|
|
|
|
|
- class="task-card"
|
|
|
|
|
- shadow="hover"
|
|
|
|
|
- :class="{ 'is-selected': isSelected(row.id) }"
|
|
|
|
|
|
|
+ <!-- 飞书多维表格风格列表 -->
|
|
|
|
|
+ <div v-loading="loading" class="todo-table-wrapper">
|
|
|
|
|
+ <el-empty v-if="!loading && filteredTableData.length === 0" description="暂无待办任务" />
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ v-else
|
|
|
|
|
+ ref="tableRef"
|
|
|
|
|
+ :data="filteredTableData"
|
|
|
|
|
+ class="todo-table"
|
|
|
|
|
+ row-key="id"
|
|
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
>
|
|
>
|
|
|
- <div class="task-card-main" @click="toggleSelect(row)">
|
|
|
|
|
- <div class="task-card-header">
|
|
|
|
|
- <el-checkbox
|
|
|
|
|
- :model-value="isSelected(row.id)"
|
|
|
|
|
- @click.stop
|
|
|
|
|
- @change="(val: any) => handleSelectChange(val, row)"
|
|
|
|
|
- />
|
|
|
|
|
- <div class="task-title">{{ row.definitionName || '未命名流程' }}</div>
|
|
|
|
|
- <el-tag v-if="row.urgency === 2" type="danger" size="small" effect="dark">已超时</el-tag>
|
|
|
|
|
- <el-tag v-else-if="row.urgency === 1" type="warning" size="small" effect="dark">即将超时</el-tag>
|
|
|
|
|
- <el-tag v-else-if="row.timeoutTime" type="info" size="small">正常</el-tag>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="task-card-body">
|
|
|
|
|
- <div class="task-meta">
|
|
|
|
|
- <span class="meta-label">当前节点</span>
|
|
|
|
|
- <el-tag type="warning" size="small">{{ row.nodeName }}</el-tag>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="task-meta">
|
|
|
|
|
- <span class="meta-label">到达时间</span>
|
|
|
|
|
- <span>{{ row.createTime }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div v-if="row.timeoutTime" class="task-meta">
|
|
|
|
|
- <span class="meta-label">剩余时间</span>
|
|
|
|
|
- <span :class="{ 'text-danger': row.urgency === 2, 'text-warning': row.urgency === 1 }">
|
|
|
|
|
- {{ formatRemaining(row) }}
|
|
|
|
|
- </span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div v-if="row.instanceTitle" class="task-meta">
|
|
|
|
|
- <span class="meta-label">实例标题</span>
|
|
|
|
|
- <el-tooltip :content="row.instanceTitle" placement="top">
|
|
|
|
|
- <span class="meta-value ellipsis">{{ row.instanceTitle }}</span>
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
|
|
+ <el-table-column type="selection" width="48" fixed="left" />
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="流程名称" min-width="200" fixed="left" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="process-cell">
|
|
|
|
|
+ <div class="process-title">
|
|
|
|
|
+ <el-tag
|
|
|
|
|
+ :type="urgencyType(row.urgency)"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ effect="dark"
|
|
|
|
|
+ class="urgency-tag"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ urgencyText(row.urgency) }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ <span class="process-name">{{ row.definitionName || '未命名流程' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="row.instanceTitle" class="process-subtitle">
|
|
|
|
|
+ {{ row.instanceTitle }}
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div v-if="row.instanceNo" class="task-meta">
|
|
|
|
|
- <span class="meta-label">实例编号</span>
|
|
|
|
|
- <span class="meta-value">{{ row.instanceNo }}</span>
|
|
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="当前节点" width="140" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag type="warning" size="small">{{ row.nodeName }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="实例编号" width="170" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="text-muted">{{ row.instanceNo || '—' }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 动态流程表单字段列 -->
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ v-for="field in dynamicFields"
|
|
|
|
|
+ :key="field.name"
|
|
|
|
|
+ :label="field.label"
|
|
|
|
|
+ :prop="field.name"
|
|
|
|
|
+ min-width="140"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="field-value">{{ getFieldDisplayValue(row, field) }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="到达时间" width="160">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="time-text">{{ row.createTime }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="剩余时间" width="120">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span v-if="row.timeoutTime" :class="{ 'text-danger': row.urgency === 2, 'text-warning': row.urgency === 1 }">
|
|
|
|
|
+ {{ formatRemaining(row) }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span v-else class="text-muted">—</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="操作" width="220" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="operation-cell">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="success"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ :icon="Check"
|
|
|
|
|
+ @click.stop="openQuickApprove(row, 'pass')"
|
|
|
|
|
+ >
|
|
|
|
|
+ 通过
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ :icon="Close"
|
|
|
|
|
+ @click.stop="openQuickApprove(row, 'reject')"
|
|
|
|
|
+ >
|
|
|
|
|
+ 拒绝
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-dropdown trigger="click" @command="(cmd: string) => handleMoreAction(cmd, row)">
|
|
|
|
|
+ <el-button link type="primary" size="small">
|
|
|
|
|
+ 更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <template #dropdown>
|
|
|
|
|
+ <el-dropdown-menu>
|
|
|
|
|
+ <el-dropdown-item command="transfer">
|
|
|
|
|
+ <el-icon><Switch /></el-icon>转办
|
|
|
|
|
+ </el-dropdown-item>
|
|
|
|
|
+ <el-dropdown-item command="addSign">
|
|
|
|
|
+ <el-icon><Plus /></el-icon>加签
|
|
|
|
|
+ </el-dropdown-item>
|
|
|
|
|
+ <el-dropdown-item command="detail" divided>
|
|
|
|
|
+ <el-icon><View /></el-icon>详情
|
|
|
|
|
+ </el-dropdown-item>
|
|
|
|
|
+ </el-dropdown-menu>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dropdown>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 悬浮快捷操作 -->
|
|
|
|
|
- <div class="task-card-actions">
|
|
|
|
|
- <el-tooltip content="通过" placement="top">
|
|
|
|
|
- <el-button circle type="success" size="small" :icon="Check" @click.stop="openQuickApprove(row, 'pass')" />
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
- <el-tooltip content="拒绝" placement="top">
|
|
|
|
|
- <el-button circle type="danger" size="small" :icon="Close" @click.stop="openQuickApprove(row, 'reject')" />
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
- <el-tooltip content="转办" placement="top">
|
|
|
|
|
- <el-button circle type="warning" size="small" :icon="Switch" @click.stop="openTransfer(row)" />
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
- <el-tooltip content="加签" placement="top">
|
|
|
|
|
- <el-button circle type="info" size="small" :icon="Plus" @click.stop="handleAddSign(row)" />
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
- <el-tooltip content="详情" placement="top">
|
|
|
|
|
- <el-button circle type="primary" size="small" :icon="View" @click.stop="handleDetail(row)" />
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-card>
|
|
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<el-pagination
|
|
<el-pagination
|
|
@@ -220,16 +272,18 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { ref, reactive, onMounted, computed } from 'vue'
|
|
import { ref, reactive, onMounted, computed } from 'vue'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
-import { Check, Close, Switch, Plus, View } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
+import { Check, Close, Switch, Plus, View, ArrowDown } from '@element-plus/icons-vue'
|
|
|
import { listTodo, approveTask, rejectTask, transferTask, addSignTask, batchApproveTask, batchRejectTask, listNextNodes } from '@/api/flow/task'
|
|
import { listTodo, approveTask, rejectTask, transferTask, addSignTask, batchApproveTask, batchRejectTask, listNextNodes } from '@/api/flow/task'
|
|
|
|
|
+import { listEnabled } from '@/api/flow/definition'
|
|
|
import { listUsersByRole } from '@/api/system/role'
|
|
import { listUsersByRole } from '@/api/system/role'
|
|
|
import { listUser } from '@/api/system/user'
|
|
import { listUser } from '@/api/system/user'
|
|
|
import { uploadFile } from '@/api/file'
|
|
import { uploadFile } from '@/api/file'
|
|
|
import { getInstanceAttachments } from '@/api/flow/instance'
|
|
import { getInstanceAttachments } from '@/api/flow/instance'
|
|
|
import { beforeFileUpload, getUploadUrl, getFileName } from '@/utils/file'
|
|
import { beforeFileUpload, getUploadUrl, getFileName } from '@/utils/file'
|
|
|
-import type { FlowTask, ApprovalAction, Attachment, NextNode } from '@/types/flow'
|
|
|
|
|
|
|
+import type { FlowTask, ApprovalAction, Attachment, NextNode, FormField, FormFieldOption, FlowDefinition } from '@/types/flow'
|
|
|
import { useUserStore } from '@/stores/user-store'
|
|
import { useUserStore } from '@/stores/user-store'
|
|
|
import type { User } from '@/types/system'
|
|
import type { User } from '@/types/system'
|
|
|
|
|
+import type { TableInstance } from 'element-plus'
|
|
|
import InstanceDetail from '@/views/flow/execute/InstanceDetail.vue'
|
|
import InstanceDetail from '@/views/flow/execute/InstanceDetail.vue'
|
|
|
import FilePreview from '@/components/FilePreview/index.vue'
|
|
import FilePreview from '@/components/FilePreview/index.vue'
|
|
|
|
|
|
|
@@ -237,6 +291,8 @@ const userStore = useUserStore()
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
|
const tableData = ref<FlowTask[]>([])
|
|
const tableData = ref<FlowTask[]>([])
|
|
|
const total = ref(0)
|
|
const total = ref(0)
|
|
|
|
|
+const tableRef = ref<TableInstance>()
|
|
|
|
|
+const definitionList = ref<FlowDefinition[]>([])
|
|
|
const queryParams = reactive({
|
|
const queryParams = reactive({
|
|
|
pageNum: 1,
|
|
pageNum: 1,
|
|
|
pageSize: 10,
|
|
pageSize: 10,
|
|
@@ -275,38 +331,86 @@ const filteredTableData = computed(() => {
|
|
|
return tableData.value.filter(row => row.urgency === map[activeUrgency.value])
|
|
return tableData.value.filter(row => row.urgency === map[activeUrgency.value])
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const userList = ref<User[]>([])
|
|
|
|
|
-const nextNodes = ref<NextNode[]>([])
|
|
|
|
|
|
|
+// 动态表单字段列:从当前页所有任务的 formSchema 聚合
|
|
|
|
|
+const dynamicFields = computed<FormField[]>(() => {
|
|
|
|
|
+ const fieldMap = new Map<string, FormField>()
|
|
|
|
|
+ filteredTableData.value.forEach(row => {
|
|
|
|
|
+ if (!row.formSchema) return
|
|
|
|
|
+ try {
|
|
|
|
|
+ const parsed = JSON.parse(row.formSchema)
|
|
|
|
|
+ if (Array.isArray(parsed)) {
|
|
|
|
|
+ parsed.forEach((field: FormField) => {
|
|
|
|
|
+ if (field.name && !fieldMap.has(field.name)) {
|
|
|
|
|
+ fieldMap.set(field.name, field)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // ignore
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ return Array.from(fieldMap.values())
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
-// 选择相关
|
|
|
|
|
-const selectedIds = ref<number[]>([])
|
|
|
|
|
|
|
+function parseFormData(row: FlowTask): Record<string, unknown> {
|
|
|
|
|
+ if (!row.formData) return {}
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.parse(row.formData)
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return {}
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-function isSelected(id: number) {
|
|
|
|
|
- return selectedIds.value.includes(id)
|
|
|
|
|
|
|
+function normalizeOptions(options?: FormFieldOption[] | string[]): FormFieldOption[] {
|
|
|
|
|
+ if (!options || !Array.isArray(options)) return []
|
|
|
|
|
+ return options.map((opt: any) => {
|
|
|
|
|
+ if (typeof opt === 'string') {
|
|
|
|
|
+ return { label: opt, value: opt }
|
|
|
|
|
+ }
|
|
|
|
|
+ return { label: String(opt.label ?? opt.value), value: opt.value }
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function toggleSelect(row: FlowTask) {
|
|
|
|
|
- const idx = selectedIds.value.indexOf(row.id)
|
|
|
|
|
- if (idx > -1) {
|
|
|
|
|
- selectedIds.value.splice(idx, 1)
|
|
|
|
|
- } else {
|
|
|
|
|
- selectedIds.value.push(row.id)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+function formatValue(value: unknown): string {
|
|
|
|
|
+ if (value === undefined || value === null) return ''
|
|
|
|
|
+ if (typeof value === 'boolean') return value ? '是' : '否'
|
|
|
|
|
+ return String(value)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function handleSelectChange(val: boolean, row: FlowTask) {
|
|
|
|
|
- if (val) {
|
|
|
|
|
- if (!selectedIds.value.includes(row.id)) {
|
|
|
|
|
- selectedIds.value.push(row.id)
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- const idx = selectedIds.value.indexOf(row.id)
|
|
|
|
|
- if (idx > -1) selectedIds.value.splice(idx, 1)
|
|
|
|
|
|
|
+function formatSelectValue(value: unknown, field: FormField): string {
|
|
|
|
|
+ const options = normalizeOptions(field.options)
|
|
|
|
|
+ if (!options.length) return formatValue(value)
|
|
|
|
|
+ const list = field.multiple && Array.isArray(value) ? value : [value]
|
|
|
|
|
+ const labels = list.map(v => {
|
|
|
|
|
+ const found = options.find(opt => String(opt.value) === String(v))
|
|
|
|
|
+ return found ? found.label : formatValue(v)
|
|
|
|
|
+ })
|
|
|
|
|
+ return labels.join(',')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getFieldDisplayValue(row: FlowTask, field: FormField): string {
|
|
|
|
|
+ const data = parseFormData(row)
|
|
|
|
|
+ const value = data[field.name]
|
|
|
|
|
+ if (value === undefined || value === null) return '—'
|
|
|
|
|
+ if (field.type === 'select') {
|
|
|
|
|
+ return formatSelectValue(value, field)
|
|
|
}
|
|
}
|
|
|
|
|
+ return formatValue(value)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const userList = ref<User[]>([])
|
|
|
|
|
+const nextNodes = ref<NextNode[]>([])
|
|
|
|
|
+
|
|
|
|
|
+// 选择相关
|
|
|
|
|
+const selectedIds = ref<number[]>([])
|
|
|
|
|
+
|
|
|
|
|
+function handleSelectionChange(selection: FlowTask[]) {
|
|
|
|
|
+ selectedIds.value = selection.map(row => row.id)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function clearSelection() {
|
|
function clearSelection() {
|
|
|
selectedIds.value = []
|
|
selectedIds.value = []
|
|
|
|
|
+ tableRef.value?.clearSelection()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 抽屉审批相关
|
|
// 抽屉审批相关
|
|
@@ -352,7 +456,7 @@ function buildActionData(): ApprovalAction {
|
|
|
comment: form.comment
|
|
comment: form.comment
|
|
|
}
|
|
}
|
|
|
if (form.action === 'transfer' && form.transferTo) {
|
|
if (form.action === 'transfer' && form.transferTo) {
|
|
|
- data.transferToUserId = Number(form.transferTo)
|
|
|
|
|
|
|
+ data.transferTo = Number(form.transferTo)
|
|
|
}
|
|
}
|
|
|
if (form.action === 'pass' && form.targetNodeId) {
|
|
if (form.action === 'pass' && form.targetNodeId) {
|
|
|
data.targetNodeId = form.targetNodeId
|
|
data.targetNodeId = form.targetNodeId
|
|
@@ -529,6 +633,20 @@ async function submitAddSign() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function handleMoreAction(cmd: string, row: FlowTask) {
|
|
|
|
|
+ switch (cmd) {
|
|
|
|
|
+ case 'transfer':
|
|
|
|
|
+ openTransfer(row)
|
|
|
|
|
+ break
|
|
|
|
|
+ case 'addSign':
|
|
|
|
|
+ handleAddSign(row)
|
|
|
|
|
+ break
|
|
|
|
|
+ case 'detail':
|
|
|
|
|
+ handleDetail(row)
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
async function loadData() {
|
|
async function loadData() {
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
try {
|
|
try {
|
|
@@ -565,9 +683,18 @@ function resetQuery() {
|
|
|
loadData()
|
|
loadData()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+async function loadDefinitions() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ definitionList.value = await listEnabled()
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ definitionList.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
loadData()
|
|
loadData()
|
|
|
loadUsers()
|
|
loadUsers()
|
|
|
|
|
+ loadDefinitions()
|
|
|
})
|
|
})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
@@ -590,6 +717,13 @@ onMounted(() => {
|
|
|
.text-warning {
|
|
.text-warning {
|
|
|
color: #e6a23c;
|
|
color: #e6a23c;
|
|
|
}
|
|
}
|
|
|
|
|
+.text-muted {
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+}
|
|
|
|
|
+.time-text {
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+}
|
|
|
.batch-toolbar {
|
|
.batch-toolbar {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
@@ -609,81 +743,68 @@ onMounted(() => {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
gap: 10px;
|
|
gap: 10px;
|
|
|
}
|
|
}
|
|
|
-.task-card-list {
|
|
|
|
|
- display: grid;
|
|
|
|
|
- grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
|
|
|
- gap: 16px;
|
|
|
|
|
-}
|
|
|
|
|
-.task-card {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- transition: all 0.2s ease;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
|
|
+
|
|
|
|
|
+/* 飞书多维表格风格列表 */
|
|
|
|
|
+.todo-table-wrapper {
|
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
}
|
|
}
|
|
|
-.task-card:hover {
|
|
|
|
|
- transform: translateY(-2px);
|
|
|
|
|
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
+.todo-table {
|
|
|
|
|
+ width: 100%;
|
|
|
}
|
|
}
|
|
|
-.task-card.is-selected {
|
|
|
|
|
- border-color: #409eff;
|
|
|
|
|
- background: #f0f9ff;
|
|
|
|
|
|
|
+.todo-table :deep(.el-table__header-wrapper th) {
|
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ padding: 12px 0;
|
|
|
}
|
|
}
|
|
|
-.task-card-main {
|
|
|
|
|
- padding-right: 48px;
|
|
|
|
|
|
|
+.todo-table :deep(.el-table__row) {
|
|
|
|
|
+ transition: background-color 0.2s ease;
|
|
|
}
|
|
}
|
|
|
-.task-card-header {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 10px;
|
|
|
|
|
- margin-bottom: 12px;
|
|
|
|
|
|
|
+.todo-table :deep(.el-table__row:hover) {
|
|
|
|
|
+ background-color: #f5f7ff !important;
|
|
|
}
|
|
}
|
|
|
-.task-title {
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- font-weight: bold;
|
|
|
|
|
- color: #303133;
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- text-overflow: ellipsis;
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
|
|
+.todo-table :deep(.el-table__cell) {
|
|
|
|
|
+ padding: 12px 0;
|
|
|
}
|
|
}
|
|
|
-.task-card-body {
|
|
|
|
|
|
|
+.process-cell {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
- gap: 8px;
|
|
|
|
|
|
|
+ gap: 4px;
|
|
|
}
|
|
}
|
|
|
-.task-meta {
|
|
|
|
|
|
|
+.process-title {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- font-size: 13px;
|
|
|
|
|
- color: #606266;
|
|
|
|
|
|
|
+ gap: 8px;
|
|
|
}
|
|
}
|
|
|
-.meta-label {
|
|
|
|
|
- color: #909399;
|
|
|
|
|
- width: 70px;
|
|
|
|
|
|
|
+.urgency-tag {
|
|
|
flex-shrink: 0;
|
|
flex-shrink: 0;
|
|
|
}
|
|
}
|
|
|
-.meta-value {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
|
|
+.process-name {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+.process-subtitle {
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 12px;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
white-space: nowrap;
|
|
|
}
|
|
}
|
|
|
-.task-card-actions {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- top: 12px;
|
|
|
|
|
- right: 12px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- opacity: 0.3;
|
|
|
|
|
- transition: opacity 0.2s ease;
|
|
|
|
|
|
|
+.field-value {
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ font-size: 13px;
|
|
|
}
|
|
}
|
|
|
-.task-card:hover .task-card-actions,
|
|
|
|
|
-.task-card.is-selected .task-card-actions {
|
|
|
|
|
- opacity: 1;
|
|
|
|
|
|
|
+.operation-cell {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
}
|
|
}
|
|
|
-.task-card-actions .el-button {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
|
|
+.operation-cell .el-button {
|
|
|
|
|
+ padding: 4px 6px;
|
|
|
}
|
|
}
|
|
|
.pagination {
|
|
.pagination {
|
|
|
margin-top: 20px;
|
|
margin-top: 20px;
|