|
|
@@ -0,0 +1,870 @@
|
|
|
+<script lang="ts" setup>
|
|
|
+import { useRouter, useRoute } from 'vue-router'
|
|
|
+import partyIDSelect from './components/partyIDSelect.vue'
|
|
|
+import GroupNameSelect from './components/GroupNameSelect.vue'
|
|
|
+import AccountSelect from './components/AccountSelect.vue'
|
|
|
+import { VueDraggable } from 'vue-draggable-plus'
|
|
|
+import AdjustmentField from './components/AdjustmentField.vue'
|
|
|
+import { cloneDeep } from 'lodash'
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+const route = useRoute()
|
|
|
+
|
|
|
+const infoData = ref({
|
|
|
+ reportName: '',
|
|
|
+ reportLevel: '',
|
|
|
+ reportDescription: ''
|
|
|
+})
|
|
|
+const pageLoading = ref(false)
|
|
|
+onMounted(() => {
|
|
|
+ if (route.query.serial_no) {
|
|
|
+ pageLoading.value = true
|
|
|
+ $api
|
|
|
+ .editReportTemplate({ serial_no: route.query.serial_no })
|
|
|
+ .then((res) => {
|
|
|
+ if (res.code !== 200) return
|
|
|
+ const data = res.data
|
|
|
+ infoData.value = {
|
|
|
+ // 如果是复制的话,清空Report Name
|
|
|
+ reportName: route.query.copy !== 't' ? data.reportName : '',
|
|
|
+ reportLevel: data.reportLevel,
|
|
|
+ reportDescription: data.reportDescription
|
|
|
+ }
|
|
|
+ fieldsList.value = data.reportFields.map((item) => {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ uniqueId: generate8DigitUnique()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ const reportAccess = data.reportAccess
|
|
|
+ accessControlType.value = reportAccess.type
|
|
|
+ specificRoles.value = {
|
|
|
+ partyId: reportAccess.partyId,
|
|
|
+ groupName: reportAccess.groupName,
|
|
|
+ systemAccount: reportAccess.systemAccount
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ pageLoading.value = false
|
|
|
+ watch(accessControlType, (newVal) => {
|
|
|
+ if (newVal === 'Specific Roles') {
|
|
|
+ // 等待下一个渲染周期结束后,获取detailRef的高度
|
|
|
+ nextTick(() => {
|
|
|
+ if (detailRef.value) {
|
|
|
+ detailRef.value.scrollIntoView({
|
|
|
+ behavior: 'smooth', // 平滑滚动
|
|
|
+ block: 'start' // 滚动到顶部对齐
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+interface Field {
|
|
|
+ uniqueId: string
|
|
|
+ field: string
|
|
|
+ title: string
|
|
|
+ displayName: string
|
|
|
+ fieldType: string
|
|
|
+ value?: string
|
|
|
+ isFilter: boolean
|
|
|
+ isSort: boolean
|
|
|
+ groupName: string
|
|
|
+}
|
|
|
+const fieldsList = ref<Field[]>([])
|
|
|
+const levelOptions = [
|
|
|
+ {
|
|
|
+ label: 'Shipment level',
|
|
|
+ value: 'Shipment level'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'Container Level',
|
|
|
+ value: 'Container Level'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'Item Level',
|
|
|
+ value: 'Item Level'
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+// 前端使用的唯一标识符
|
|
|
+const generate8DigitUnique = () => {
|
|
|
+ return Math.floor(10000000 + Math.random() * 90000000).toString()
|
|
|
+}
|
|
|
+
|
|
|
+const changeFieldConfig = (state: any, field: string, index: number, key: string) => {
|
|
|
+ if (!state) return
|
|
|
+ fieldsList.value.forEach((item) => {
|
|
|
+ if (item.field === field) {
|
|
|
+ item[key] = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ const firstMatch = fieldsList.value[index]
|
|
|
+ if (firstMatch) {
|
|
|
+ firstMatch[key] = true
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleDeleteField = (index: number, uniqueId: string) => {
|
|
|
+ fieldsList.value.splice(index, 1)
|
|
|
+
|
|
|
+ Object.keys(copyFieldsList.value).forEach((key) => {
|
|
|
+ copyFieldsList.value[key] = copyFieldsList.value[key].filter(
|
|
|
+ (item) => item.uniqueId !== uniqueId
|
|
|
+ )
|
|
|
+ })
|
|
|
+}
|
|
|
+const copyFieldsList = ref({})
|
|
|
+const handleCopyField = (index: number) => {
|
|
|
+ const curField = cloneDeep(fieldsList.value[index])
|
|
|
+ curField.displayName = `${curField.displayName} (Copy)`
|
|
|
+ curField.isFilter = false
|
|
|
+ curField.isSort = false
|
|
|
+ curField.uniqueId = generate8DigitUnique()
|
|
|
+ if (copyFieldsList.value[curField.field]) {
|
|
|
+ copyFieldsList.value[curField.field].push(cloneDeep(curField))
|
|
|
+ } else {
|
|
|
+ copyFieldsList.value[curField.field] = [cloneDeep(curField)]
|
|
|
+ }
|
|
|
+ fieldsList.value.splice(index + 1, 0, cloneDeep(curField))
|
|
|
+}
|
|
|
+
|
|
|
+const AdjustmentFieldRef = ref()
|
|
|
+// 打开定制表格弹窗
|
|
|
+const handleCustomizeColumns = () => {
|
|
|
+ if (!infoData.value.reportLevel) {
|
|
|
+ ElMessage.warning('Please select the report level.')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const params = {
|
|
|
+ serial_no: '',
|
|
|
+ level: infoData.value.reportLevel
|
|
|
+ }
|
|
|
+ const seen = new Set()
|
|
|
+ const uniqueArray = fieldsList.value.filter((item) => {
|
|
|
+ if (seen.has(item.field)) {
|
|
|
+ return false // 已存在,跳过
|
|
|
+ }
|
|
|
+ seen.add(item.field)
|
|
|
+ return true // 第一次出现,保留
|
|
|
+ })
|
|
|
+
|
|
|
+ AdjustmentFieldRef.value.openDialog(
|
|
|
+ params,
|
|
|
+ -220,
|
|
|
+ 'Drag item over to this selection or click "add" icon to show the field on report template list',
|
|
|
+ uniqueArray
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const newFieldInfo = ref<{
|
|
|
+ name: string
|
|
|
+ fieldType: 'Blank' | 'Fixed Value'
|
|
|
+ value: string
|
|
|
+}>({
|
|
|
+ name: '',
|
|
|
+ fieldType: 'Blank',
|
|
|
+ value: ''
|
|
|
+})
|
|
|
+const addNewFieldVisible = ref(false)
|
|
|
+// 添加新字段
|
|
|
+const handleAddNewField = () => {
|
|
|
+ addNewFieldVisible.value = true
|
|
|
+}
|
|
|
+const handleFieldTypeChange = () => {
|
|
|
+ newFieldInfo.value.value = ''
|
|
|
+}
|
|
|
+const addNewField = () => {
|
|
|
+ if (!newFieldInfo.value.name?.trim()) {
|
|
|
+ ElMessage.warning('Please enter the new field name.')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newFieldInfo.value.fieldType === 'Fixed Value' && !newFieldInfo.value.value?.trim()) {
|
|
|
+ ElMessage.warning('Please enter the fixed value.')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ fieldsList.value.unshift({
|
|
|
+ uniqueId: generate8DigitUnique(),
|
|
|
+ field: newFieldInfo.value.name,
|
|
|
+ title: newFieldInfo.value.name,
|
|
|
+ displayName: newFieldInfo.value.name,
|
|
|
+ value: newFieldInfo.value.value,
|
|
|
+ fieldType: 'Custom',
|
|
|
+ groupName: '',
|
|
|
+ isFilter: false,
|
|
|
+ isSort: false
|
|
|
+ })
|
|
|
+ newFieldInfo.value = {
|
|
|
+ name: '',
|
|
|
+ fieldType: 'Blank',
|
|
|
+ value: ''
|
|
|
+ }
|
|
|
+ addNewFieldVisible.value = false
|
|
|
+}
|
|
|
+// 调整应用字段
|
|
|
+const handleApplay = (data: any) => {
|
|
|
+ const customizeData = fieldsList.value.filter((item: any) => item.field_type === 'Custom')
|
|
|
+ fieldsList.value = data.map((item: any) => {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ label: item.label,
|
|
|
+ title: item.title || item.label,
|
|
|
+ displayName: item.displayName || item.label,
|
|
|
+ isFilter: !!item.isFilter,
|
|
|
+ isSort: !!item.isSort,
|
|
|
+ uniqueId: item.uniqueId || generate8DigitUnique()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ fieldsList.value = [...customizeData, ...fieldsList.value]
|
|
|
+
|
|
|
+ const validFields = new Set(fieldsList.value.map((item) => item.field))
|
|
|
+ for (const field in copyFieldsList.value) {
|
|
|
+ if (!validFields.has(field)) {
|
|
|
+ delete copyFieldsList.value[field]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // === 第三步:从后往前插入副本(不修改原始项)===
|
|
|
+ for (let i = fieldsList.value.length - 1; i >= 0; i--) {
|
|
|
+ const field = fieldsList.value[i].field
|
|
|
+ const copies = copyFieldsList.value[field]
|
|
|
+
|
|
|
+ if (copies?.length) {
|
|
|
+ // 插入副本到原项后面
|
|
|
+ fieldsList.value.splice(i + 1, 0, ...copies)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const accessControlType = ref('All Users')
|
|
|
+const detailRef: Ref<HTMLElement | null> = ref(null)
|
|
|
+
|
|
|
+const specificRoles = ref({
|
|
|
+ partyId: [],
|
|
|
+ groupName: [],
|
|
|
+ systemAccount: []
|
|
|
+})
|
|
|
+const changePartyId = (val: string[]) => {
|
|
|
+ specificRoles.value.partyId = val
|
|
|
+}
|
|
|
+const changeGroupName = (val: string[]) => {
|
|
|
+ specificRoles.value.groupName = val
|
|
|
+}
|
|
|
+
|
|
|
+const changeAccount = (val: string[]) => {
|
|
|
+ specificRoles.value.systemAccount = val
|
|
|
+}
|
|
|
+
|
|
|
+const fieldLoading = ref(false)
|
|
|
+const handleRightRemove = () => {}
|
|
|
+
|
|
|
+const handleCancel = () => {
|
|
|
+ router.push('/template-management')
|
|
|
+}
|
|
|
+
|
|
|
+const handlePageSave = () => {
|
|
|
+ let verified = true
|
|
|
+ if (!infoData.value.reportName.trim()) {
|
|
|
+ ElMessage.warning('Please enter the Report Name.')
|
|
|
+ verified = false
|
|
|
+ }
|
|
|
+ if (!infoData.value.reportLevel) {
|
|
|
+ ElMessage.warning('Please enter the Report Level')
|
|
|
+ verified = false
|
|
|
+ }
|
|
|
+ if (!infoData.value.reportDescription.trim()) {
|
|
|
+ ElMessage.warning('Please enter the Report Description.')
|
|
|
+ verified = false
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ accessControlType.value === 'Specific Roles' &&
|
|
|
+ specificRoles.value.partyId?.length === 0 &&
|
|
|
+ specificRoles.value.groupName?.length === 0 &&
|
|
|
+ specificRoles.value.systemAccount?.length === 0
|
|
|
+ ) {
|
|
|
+ ElMessage.warning('Please select Party ID or Group Name for Specific Roles access control.')
|
|
|
+ verified = false
|
|
|
+ }
|
|
|
+ if (!verified) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ pageLoading.value = true
|
|
|
+ const data = {
|
|
|
+ report_name: infoData.value.reportName,
|
|
|
+ report_level: infoData.value.reportLevel,
|
|
|
+ report_description: infoData.value.reportDescription,
|
|
|
+ access_type: accessControlType.value,
|
|
|
+ party_ids: specificRoles.value.partyId || [],
|
|
|
+ group_names: specificRoles.value.groupName || [],
|
|
|
+ system_account: specificRoles.value.systemAccount || [],
|
|
|
+ fieldsList: fieldsList.value
|
|
|
+ }
|
|
|
+ let serial_no = ''
|
|
|
+ if (route.query.copy !== 't' && route.query.serial_no) {
|
|
|
+ serial_no = String(route.query.serial_no)
|
|
|
+ }
|
|
|
+ $api
|
|
|
+ .saveNewReportTemplate({ ...data, serial_no })
|
|
|
+ .then((res: any) => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('Report Template saved successfully!')
|
|
|
+ router.push('/template-management')
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.data.msg || 'Failed to save Report Template.')
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ pageLoading.value = false
|
|
|
+ })
|
|
|
+}
|
|
|
+</script>
|
|
|
+<template>
|
|
|
+ <div class="dashboard" v-vloading="pageLoading">
|
|
|
+ <div class="Title">
|
|
|
+ <span>Create New Report Template</span>
|
|
|
+ <div class="button-group">
|
|
|
+ <el-button type="default" @click="handleCancel">
|
|
|
+ <span class="font_family icon-icon_return_b" style="margin-right: 3px"></span
|
|
|
+ >Cancel</el-button
|
|
|
+ >
|
|
|
+ <el-button class="el-button--main" @click="handlePageSave">
|
|
|
+ <span class="font_family icon-icon_save_b" style="margin-right: 3px"></span
|
|
|
+ >Save</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="display">
|
|
|
+ <div class="basic-info template-box">
|
|
|
+ <div class="header">Basic Report Information</div>
|
|
|
+ <div class="content-box">
|
|
|
+ <div class="info-item" style="display: flex; gap: 8px">
|
|
|
+ <div class="report-name" style="flex: 1">
|
|
|
+ <div class="label">
|
|
|
+ <span style="color: var(--color-danger)">*</span>
|
|
|
+ <span>Report Name</span>
|
|
|
+ </div>
|
|
|
+ <el-input v-model="infoData.reportName" placeholder="Please enter..."></el-input>
|
|
|
+ </div>
|
|
|
+ <div class="report-level" style="flex: 1">
|
|
|
+ <div class="label">
|
|
|
+ <span style="color: var(--color-danger)">*</span>
|
|
|
+ <span>Report Level</span>
|
|
|
+ </div>
|
|
|
+ <el-select v-model="infoData.reportLevel" placeholder="Please enter...">
|
|
|
+ <el-option
|
|
|
+ v-for="item in levelOptions"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ :key="item.value"
|
|
|
+ ></el-option>
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <div class="label">
|
|
|
+ <span style="color: var(--color-danger)">*</span>
|
|
|
+ <span>Report Description</span>
|
|
|
+ </div>
|
|
|
+ <el-input
|
|
|
+ type="textarea"
|
|
|
+ v-model="infoData.reportDescription"
|
|
|
+ placeholder="Please enter..."
|
|
|
+ ></el-input>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="fields-configuration template-box">
|
|
|
+ <div class="header">
|
|
|
+ <span>Report Fields Configuration</span>
|
|
|
+
|
|
|
+ <div class="right-option">
|
|
|
+ <el-button
|
|
|
+ class="el-button--dark"
|
|
|
+ @click="handleAddNewField"
|
|
|
+ style="width: 148px; padding-top: 11px"
|
|
|
+ >
|
|
|
+ <span style="margin-right: 3px" class="font_family icon-icon_add_b"></span>
|
|
|
+ <span>Add New Field</span>
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="fieldsList.length > 0"
|
|
|
+ class="el-button--dark"
|
|
|
+ @click="handleCustomizeColumns()"
|
|
|
+ style="width: 110px; padding-top: 11px"
|
|
|
+ >
|
|
|
+ <span style="margin-right: 3px" class="font_family icon-icon_add_b"></span>
|
|
|
+ <span>Select Field</span>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="content-box">
|
|
|
+ <div class="empty-box" v-if="fieldsList.length === 0">
|
|
|
+ <el-button class="el-button--dark" @click="handleCustomizeColumns">
|
|
|
+ <span class="font_family icon-icon_add_b"></span>Add/Edit Field
|
|
|
+ </el-button>
|
|
|
+ <p>No field selected. click “Add Field” to get started.</p>
|
|
|
+ </div>
|
|
|
+ <div class="fields-list" v-else>
|
|
|
+ <VueDraggable
|
|
|
+ v-vloading="fieldLoading"
|
|
|
+ v-model="fieldsList"
|
|
|
+ class="column-list"
|
|
|
+ ghost-class="ghost-column"
|
|
|
+ :forceFallback="true"
|
|
|
+ fallback-class="fallback-class"
|
|
|
+ group="customizeColumns"
|
|
|
+ item-key="uniqueId"
|
|
|
+ @end="handleRightRemove"
|
|
|
+ handle=".handle-draggable"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="field-item"
|
|
|
+ v-for="(fieldItem, index) in fieldsList"
|
|
|
+ :key="fieldItem.uniqueId"
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ class="font_family icon-icon_dragsort__b draggable-icon handle-draggable"
|
|
|
+ style="margin-right: 12px; font-size: 16px"
|
|
|
+ ></span>
|
|
|
+ <div class="label handle-draggable">
|
|
|
+ <span style="font-weight: 700">[{{ fieldItem.field }}]</span>
|
|
|
+ <span style="margin-left: 8px">{{ fieldItem.title }}</span>
|
|
|
+ </div>
|
|
|
+ <el-input
|
|
|
+ :id="fieldItem.uniqueId"
|
|
|
+ :name="fieldItem.uniqueId"
|
|
|
+ class="display-name"
|
|
|
+ v-model="fieldItem.displayName"
|
|
|
+ placeholder="Display Name in Report"
|
|
|
+ ></el-input>
|
|
|
+ <div class="actions">
|
|
|
+ <div class="checkbox-group">
|
|
|
+ <el-checkbox
|
|
|
+ :disabled="
|
|
|
+ fieldItem.fieldType !== 'System' ||
|
|
|
+ fieldItem.groupName === 'Container Status' ||
|
|
|
+ fieldItem.groupName === 'Milestone'
|
|
|
+ "
|
|
|
+ v-model="fieldItem.isFilter"
|
|
|
+ @change="changeFieldConfig($event, fieldItem.field, index, 'isFilter')"
|
|
|
+ >Filter</el-checkbox
|
|
|
+ >
|
|
|
+ <el-checkbox
|
|
|
+ :disabled="fieldItem.fieldType !== 'System'"
|
|
|
+ v-model="fieldItem.isSort"
|
|
|
+ @change="changeFieldConfig($event, fieldItem.field, index, 'isSort')"
|
|
|
+ >Sort</el-checkbox
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <span
|
|
|
+ style="margin-right: 4px"
|
|
|
+ @click="handleCopyField(index)"
|
|
|
+ class="font_family icon-icon_clone_b"
|
|
|
+ ></span>
|
|
|
+ <span
|
|
|
+ @click="handleDeleteField(index, fieldItem.uniqueId)"
|
|
|
+ class="font_family icon-icon_delete_b"
|
|
|
+ ></span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </VueDraggable>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="report-access-control template-box">
|
|
|
+ <div class="header">Report Access Control</div>
|
|
|
+ <div class="content-box">
|
|
|
+ <el-radio-group class="radio-group" v-model="accessControlType">
|
|
|
+ <el-radio class="radio-item" value="All Users">
|
|
|
+ <template #default>
|
|
|
+ <div class="radio-content">
|
|
|
+ <p class="label">All Users</p>
|
|
|
+ <p class="description">
|
|
|
+ This report will be available to all users in the system
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-radio>
|
|
|
+ <el-radio class="radio-item specific-roles" value="Specific Roles">
|
|
|
+ <template #default>
|
|
|
+ <div class="radio-content">
|
|
|
+ <div class="top-options">
|
|
|
+ <p class="label">Specific Roles</p>
|
|
|
+ <p class="description">Restrict access to specific user roles</p>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="extended-filter"
|
|
|
+ v-show="accessControlType === 'Specific Roles'"
|
|
|
+ ref="detailRef"
|
|
|
+ >
|
|
|
+ <div class="dividing-line"></div>
|
|
|
+ <div class="filter-item" style="margin-bottom: 16px">
|
|
|
+ <div class="label">
|
|
|
+ <span style="color: var(--color-danger)">*</span>
|
|
|
+ <span>Party ID</span>
|
|
|
+ </div>
|
|
|
+ <partyIDSelect
|
|
|
+ @change-data="changePartyId"
|
|
|
+ :data="specificRoles.partyId"
|
|
|
+ ></partyIDSelect>
|
|
|
+ </div>
|
|
|
+ <div class="filter-item" style="margin-bottom: 16px">
|
|
|
+ <div class="label">
|
|
|
+ <span style="color: var(--color-danger)">*</span>
|
|
|
+ <span>Group Name</span>
|
|
|
+ </div>
|
|
|
+ <GroupNameSelect
|
|
|
+ @change-data="changeGroupName"
|
|
|
+ :data="specificRoles.groupName"
|
|
|
+ ></GroupNameSelect>
|
|
|
+ </div>
|
|
|
+ <div class="filter-item">
|
|
|
+ <div class="label">
|
|
|
+ <span style="color: var(--color-danger)">*</span>
|
|
|
+ <span>KLN ONLINE Account</span>
|
|
|
+ </div>
|
|
|
+ <AccountSelect
|
|
|
+ @change-data="changeAccount"
|
|
|
+ :data="specificRoles.systemAccount"
|
|
|
+ ></AccountSelect>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <AdjustmentField @apply="handleApplay" ref="AdjustmentFieldRef" />
|
|
|
+ <el-dialog
|
|
|
+ class="add-new-field-dialog"
|
|
|
+ title="Add New Field"
|
|
|
+ v-model="addNewFieldVisible"
|
|
|
+ width="480"
|
|
|
+ >
|
|
|
+ <div>
|
|
|
+ <div class="field-item">
|
|
|
+ <div class="label">
|
|
|
+ <span class="required-symbol">*</span>
|
|
|
+ <span>New Field Name</span>
|
|
|
+ </div>
|
|
|
+ <el-input placeholder="Please enter..." v-model="newFieldInfo.name"></el-input>
|
|
|
+ </div>
|
|
|
+ <div class="field-item field-value">
|
|
|
+ <div class="label">
|
|
|
+ <span class="required-symbol">*</span>
|
|
|
+ <span>Field Value</span>
|
|
|
+ </div>
|
|
|
+ <el-radio-group v-model="newFieldInfo.fieldType" @change="handleFieldTypeChange">
|
|
|
+ <el-radio label="Blank">Blank</el-radio>
|
|
|
+ <el-radio label="Fixed Value">Fixed Value</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <div class="field-item" v-if="newFieldInfo.fieldType === 'Fixed Value'">
|
|
|
+ <div class="label">
|
|
|
+ <span class="required-symbol">*</span>
|
|
|
+ <span>Fixed Value</span>
|
|
|
+ </div>
|
|
|
+ <el-input placeholder="Please enter..." v-model="newFieldInfo.value"></el-input>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <el-button
|
|
|
+ style="height: 40px; width: 115px"
|
|
|
+ class="cancel-btn"
|
|
|
+ type="default"
|
|
|
+ @click="addNewFieldVisible = false"
|
|
|
+ >Cancel</el-button
|
|
|
+ >
|
|
|
+ <el-button style="height: 40px; width: 120px" class="el-button--dark" @click="addNewField"
|
|
|
+ >Apply</el-button
|
|
|
+ >
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.Title {
|
|
|
+ position: sticky;
|
|
|
+ top: 0;
|
|
|
+ z-index: 100;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ height: 68px;
|
|
|
+ border-bottom: 1px solid var(--color-border);
|
|
|
+ font-size: var(--font-size-6);
|
|
|
+ font-weight: 700;
|
|
|
+ padding: 0 24px;
|
|
|
+ align-items: center;
|
|
|
+ background-color: var(--color-mode);
|
|
|
+}
|
|
|
+.heaer_top {
|
|
|
+ margin-top: 6.57px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ padding-right: 8px;
|
|
|
+ display: flex;
|
|
|
+}
|
|
|
+
|
|
|
+.display {
|
|
|
+ max-height: calc(100vh - 140px);
|
|
|
+ border: 1px solid var(--color-border);
|
|
|
+ border-bottom: none;
|
|
|
+ border-width: 0 0 1px 0;
|
|
|
+ padding: 16px 24px 12px;
|
|
|
+ overflow: auto;
|
|
|
+}
|
|
|
+.template-box {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ border: 1px solid var(--color-border);
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ .header {
|
|
|
+ height: 48px;
|
|
|
+ border-bottom: 1px solid var(--color-border);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 16px;
|
|
|
+ border-radius: 12px 12px 0 0;
|
|
|
+ background-color: var(--color-header-bg);
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ .right-option {
|
|
|
+ margin-left: auto;
|
|
|
+ }
|
|
|
+ .content-box {
|
|
|
+ height: 100%;
|
|
|
+ padding: 8px 16px 16px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.fields-configuration {
|
|
|
+ div.content-box {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ justify-content: center;
|
|
|
+ min-height: 272px;
|
|
|
+ max-height: 400px;
|
|
|
+ width: 100%;
|
|
|
+ padding-bottom: 8px;
|
|
|
+ padding-right: 0px;
|
|
|
+ // overflow: auto;
|
|
|
+ .empty-box {
|
|
|
+ align-self: center;
|
|
|
+ width: 100%;
|
|
|
+ text-align: center;
|
|
|
+ p {
|
|
|
+ margin-top: 12px;
|
|
|
+ color: var(--color-neutral-2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .fields-list {
|
|
|
+ width: 100%;
|
|
|
+ max-height: 400px;
|
|
|
+ padding: 8px 0;
|
|
|
+ padding-right: 16px;
|
|
|
+ overflow: auto;
|
|
|
+ user-select: none;
|
|
|
+ .field-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 48px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ padding: 0 16px;
|
|
|
+ border-radius: 6px;
|
|
|
+ border: 1px solid var(--color-border);
|
|
|
+ .label {
|
|
|
+ flex: 1;
|
|
|
+ .required-symbol {
|
|
|
+ color: var(--color-danger);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .display-name {
|
|
|
+ flex: 1.2;
|
|
|
+ margin: 0 16px;
|
|
|
+ :deep(.el-input__wrapper) {
|
|
|
+ height: 32px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ width: 240px;
|
|
|
+ padding-left: 30px;
|
|
|
+ .checkbox-group {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+ .el-checkbox {
|
|
|
+ margin-right: 16px;
|
|
|
+ :deep(.el-checkbox__inner) {
|
|
|
+ height: 16px;
|
|
|
+ width: 16px;
|
|
|
+ &::after {
|
|
|
+ border-width: 2px;
|
|
|
+ height: 9px;
|
|
|
+ width: 5px;
|
|
|
+ left: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ :deep(.el-checkbox__label) {
|
|
|
+ margin-top: 3px;
|
|
|
+ padding-left: 4px;
|
|
|
+ line-height: 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .font_family {
|
|
|
+ float: right;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .ghost-column {
|
|
|
+ cursor: move !important;
|
|
|
+ span {
|
|
|
+ opacity: 0;
|
|
|
+ }
|
|
|
+ border: 1px dashed var(--color-customize-column-item-drag-border) !important;
|
|
|
+ background-color: var(--color-customize-column-item-drag-bg) !important;
|
|
|
+ box-shadow: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fallback-class {
|
|
|
+ opacity: 1 !important;
|
|
|
+ background-color: var(--color-customize-column-item-hover-bg) !important;
|
|
|
+ cursor: move !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+.basic-info {
|
|
|
+ .info-item {
|
|
|
+ &:first-child {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+ .label {
|
|
|
+ margin-bottom: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ span {
|
|
|
+ color: var(--color-neutral-2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.report-access-control {
|
|
|
+ .content-box {
|
|
|
+ .radio-group {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+ .radio-item {
|
|
|
+ align-items: flex-start;
|
|
|
+ width: 100%;
|
|
|
+ // min-height: 80px;
|
|
|
+ height: auto;
|
|
|
+ margin-right: 24px;
|
|
|
+ margin-top: 4px;
|
|
|
+ padding: 20px 16px;
|
|
|
+ font-size: 14px;
|
|
|
+ border: 1px solid var(--color-border);
|
|
|
+ border-radius: 12px;
|
|
|
+ .radio-content {
|
|
|
+ height: auto;
|
|
|
+ }
|
|
|
+ // .top-options {
|
|
|
+ .label {
|
|
|
+ font-weight: 700;
|
|
|
+ }
|
|
|
+ .description {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--color-neutral-2);
|
|
|
+ }
|
|
|
+ // }
|
|
|
+ }
|
|
|
+ .specific-roles {
|
|
|
+ position: relative;
|
|
|
+ .dividing-line {
|
|
|
+ position: absolute;
|
|
|
+ left: 0px;
|
|
|
+ top: 66px;
|
|
|
+ margin: 12px 0;
|
|
|
+ height: 1px;
|
|
|
+ width: 100%;
|
|
|
+ background-color: var(--color-border);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .extended-filter {
|
|
|
+ margin-top: 28px;
|
|
|
+ border-radius: 6px;
|
|
|
+
|
|
|
+ .filter-item {
|
|
|
+ .label {
|
|
|
+ margin-bottom: 4px;
|
|
|
+ span {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--color-neutral-2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // .radio-item.specific-roles {
|
|
|
+ // padding: 0;
|
|
|
+ // }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.dashboard {
|
|
|
+ position: relative;
|
|
|
+ background-color: var(--color-mode);
|
|
|
+ .button-group {
|
|
|
+ .el-button {
|
|
|
+ height: 40px;
|
|
|
+ padding: 8px 32px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+<style lang="scss">
|
|
|
+.add-new-field-dialog {
|
|
|
+ .field-item {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ .label {
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .field-value {
|
|
|
+ .el-radio-group {
|
|
|
+ width: 100%;
|
|
|
+ .el-radio {
|
|
|
+ flex: 1;
|
|
|
+ margin-right: 0;
|
|
|
+ padding-left: 12px;
|
|
|
+ border: 1px solid var(--color-border);
|
|
|
+ &:first-child {
|
|
|
+ border-radius: 6px 0 0 6px;
|
|
|
+ border-right: none;
|
|
|
+ }
|
|
|
+ &:last-child {
|
|
|
+ border-radius: 0 6px 6px 0;
|
|
|
+ }
|
|
|
+ .el-radio__label {
|
|
|
+ color: var(--color-neutral-1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|