|
|
@@ -1,19 +1,23 @@
|
|
|
<script setup lang="ts">
|
|
|
-import { onMounted, reactive, ref } from 'vue'
|
|
|
+import { computed, onMounted, reactive, ref } from 'vue'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import {
|
|
|
Delete,
|
|
|
+ Download,
|
|
|
Edit,
|
|
|
Eye,
|
|
|
+ FileText,
|
|
|
Home,
|
|
|
Info,
|
|
|
Package,
|
|
|
Plus,
|
|
|
Search,
|
|
|
Settings,
|
|
|
+ Users,
|
|
|
Wrench,
|
|
|
} from 'lucide-vue-next'
|
|
|
import { clientGet, clientPost } from '@/utils/request.ts'
|
|
|
+import { renderAsync } from 'docx-preview'
|
|
|
|
|
|
interface ASimplifiedHouseInfo {
|
|
|
id: string
|
|
|
@@ -155,12 +159,45 @@ interface UpdateAMaintenanceRecords {
|
|
|
maintenanceStatus: string
|
|
|
}
|
|
|
|
|
|
+interface ATenantInfo {
|
|
|
+ id: string
|
|
|
+ simplifiedHouseId: string
|
|
|
+ tenantName: string
|
|
|
+ tenantNumber: string
|
|
|
+ tenantIdCard: string
|
|
|
+ tenantInDate: string
|
|
|
+ tenantTime: string
|
|
|
+ tenantRent: number
|
|
|
+}
|
|
|
+
|
|
|
+interface ATenantInfoResponse extends BaseResponse {
|
|
|
+ data: ATenantInfo
|
|
|
+}
|
|
|
+
|
|
|
+interface AContractInfo {
|
|
|
+ id: string
|
|
|
+ simplifiedHouseId: string
|
|
|
+ contractNumber: string
|
|
|
+ contractDate: string
|
|
|
+ contractTime: string
|
|
|
+ contractExpirationDate: string
|
|
|
+ contractDeposit: number
|
|
|
+ contractStatus: string
|
|
|
+ originalContractUrl: string
|
|
|
+ signContractUrl: string
|
|
|
+}
|
|
|
+
|
|
|
+interface AContractInfoResponse extends BaseResponse {
|
|
|
+ data: AContractInfo
|
|
|
+}
|
|
|
+
|
|
|
// 响应式数据
|
|
|
const loading = ref(false)
|
|
|
const tableData = ref<ASimplifiedHouseInfo[]>([])
|
|
|
const total = ref(0)
|
|
|
const currentPage = ref(1)
|
|
|
const pageSize = ref(10)
|
|
|
+const MINIO_URL = import.meta.env.VITE_MINIO_BASE_URL
|
|
|
|
|
|
// 搜索表单
|
|
|
const searchForm = reactive({
|
|
|
@@ -176,6 +213,7 @@ const detailDialogVisible = ref(false)
|
|
|
const deviceDialogVisible = ref(false)
|
|
|
const maintenanceDialogVisible = ref(false)
|
|
|
const maintenanceRecordDialogVisible = ref(false)
|
|
|
+const contractPreviewDialogVisible = ref(false)
|
|
|
const isEdit = ref(false)
|
|
|
const isDeviceEdit = ref(false)
|
|
|
const isMaintenanceEdit = ref(false)
|
|
|
@@ -229,9 +267,26 @@ const maintenanceForm = reactive<AddAMaintenanceRecords & { id?: string }>({
|
|
|
const selectedRows = ref<ASimplifiedHouseInfo[]>([])
|
|
|
const currentHouseId = ref('')
|
|
|
const currentHouseName = ref('')
|
|
|
+const currentHouseStatus = ref('')
|
|
|
const deviceList = ref<ADeviceInfo[]>([])
|
|
|
const deviceLoading = ref(false)
|
|
|
|
|
|
+// 租户和合同信息
|
|
|
+const tenantInfo = ref<ATenantInfo | null>(null)
|
|
|
+const contractInfo = ref<AContractInfo | null>(null)
|
|
|
+const tenantLoading = ref(false)
|
|
|
+const contractLoading = ref(false)
|
|
|
+
|
|
|
+// 合同预览相关
|
|
|
+const previewLoading = ref(false)
|
|
|
+const previewTitle = ref('')
|
|
|
+const previewContent = ref<HTMLElement | null>(null)
|
|
|
+
|
|
|
+// 计算属性:是否显示租户和合同标签页
|
|
|
+const showTenantAndContractTabs = computed(() => {
|
|
|
+ return currentHouseStatus.value === '已租'
|
|
|
+})
|
|
|
+
|
|
|
// API方法
|
|
|
const addRentingHouse = async (info: AddASimplifiedHouseInfo) => {
|
|
|
const res = await clientPost<AddASimplifiedHouseInfo, BaseResponse>(
|
|
|
@@ -427,6 +482,112 @@ const getDeviceMaintainRecord = async (deviceId: string) => {
|
|
|
return res.data
|
|
|
}
|
|
|
|
|
|
+// 根据房屋ID获取租户信息
|
|
|
+const getTenantInfo = async (simplifiedHouseId: string) => {
|
|
|
+ tenantLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await clientGet<
|
|
|
+ {
|
|
|
+ simplifiedHouseId: string
|
|
|
+ },
|
|
|
+ ATenantInfoResponse
|
|
|
+ >('/atenantInfo/getBySimplifiedHouseId', {
|
|
|
+ params: {
|
|
|
+ simplifiedHouseId: simplifiedHouseId,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg)
|
|
|
+ tenantInfo.value = null
|
|
|
+ return
|
|
|
+ }
|
|
|
+ tenantInfo.value = res.data
|
|
|
+ } finally {
|
|
|
+ tenantLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 根据房屋ID获取合同信息
|
|
|
+const getContractInfo = async (simplifiedHouseId: string) => {
|
|
|
+ contractLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await clientGet<
|
|
|
+ {
|
|
|
+ simplifiedHouseId: string
|
|
|
+ },
|
|
|
+ AContractInfoResponse
|
|
|
+ >('/acontractInfo/getBySimplifiedHouseId', {
|
|
|
+ params: {
|
|
|
+ simplifiedHouseId: simplifiedHouseId,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg)
|
|
|
+ contractInfo.value = null
|
|
|
+ return
|
|
|
+ }
|
|
|
+ contractInfo.value = res.data
|
|
|
+ } finally {
|
|
|
+ contractLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 预览docx文件
|
|
|
+const previewDocx = async (url: string, title: string) => {
|
|
|
+ url = MINIO_URL + url
|
|
|
+ if (!url) {
|
|
|
+ ElMessage.warning('文档地址为空')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ previewLoading.value = true
|
|
|
+ previewTitle.value = title
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取文档内容
|
|
|
+ const response = await fetch(url)
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error('文档加载失败')
|
|
|
+ }
|
|
|
+
|
|
|
+ const arrayBuffer = await response.arrayBuffer()
|
|
|
+
|
|
|
+ // 创建预览容器
|
|
|
+ const container = document.createElement('div')
|
|
|
+ container.style.padding = '20px'
|
|
|
+ container.style.backgroundColor = '#fff'
|
|
|
+ container.style.minHeight = '400px'
|
|
|
+
|
|
|
+ // 使用docx-preview渲染文档
|
|
|
+ await renderAsync(arrayBuffer, container)
|
|
|
+
|
|
|
+ previewContent.value = container
|
|
|
+ contractPreviewDialogVisible.value = true
|
|
|
+ } catch (error) {
|
|
|
+ console.error('预览文档失败:', error)
|
|
|
+ ElMessage.error('预览文档失败,请检查文档地址是否正确')
|
|
|
+ } finally {
|
|
|
+ previewLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 下载文档
|
|
|
+const downloadDocument = (url: string, filename: string) => {
|
|
|
+ url = MINIO_URL + url
|
|
|
+ if (!url) {
|
|
|
+ ElMessage.warning('文档地址为空')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.href = url
|
|
|
+ link.download = filename
|
|
|
+ link.target = '_blank'
|
|
|
+ document.body.appendChild(link)
|
|
|
+ link.click()
|
|
|
+ document.body.removeChild(link)
|
|
|
+}
|
|
|
+
|
|
|
// 页面方法
|
|
|
const handleSearch = () => {
|
|
|
currentPage.value = 1
|
|
|
@@ -506,6 +667,7 @@ const handleBatchDelete = async () => {
|
|
|
const handleViewDetail = async (row: ASimplifiedHouseInfo) => {
|
|
|
currentHouseId.value = row.id
|
|
|
currentHouseName.value = row.houseName || ''
|
|
|
+ currentHouseStatus.value = row.status || ''
|
|
|
activeTab.value = 'detail'
|
|
|
|
|
|
// 获取房屋详细信息
|
|
|
@@ -525,6 +687,12 @@ const handleViewDetail = async (row: ASimplifiedHouseInfo) => {
|
|
|
// 获取设备列表
|
|
|
await loadDeviceList(row.id)
|
|
|
|
|
|
+ // 如果是已租房屋,获取租户和合同信息
|
|
|
+ if (row.status === '已租') {
|
|
|
+ await getTenantInfo(row.id)
|
|
|
+ await getContractInfo(row.id)
|
|
|
+ }
|
|
|
+
|
|
|
detailDialogVisible.value = true
|
|
|
}
|
|
|
|
|
|
@@ -721,8 +889,16 @@ const getAssetTypeTag = (assetType: string | null | undefined): string => {
|
|
|
if (!assetType) return 'default'
|
|
|
if (assetType === '公租房') return 'primary'
|
|
|
if (assetType === '厂房') return 'success'
|
|
|
- if (assetType === '创新创业基地') return 'danger' // 新增类型
|
|
|
- return 'warning' // 默认颜色
|
|
|
+ if (assetType === '创新创业基地') return 'danger'
|
|
|
+ return 'warning'
|
|
|
+}
|
|
|
+
|
|
|
+const getContractStatusColor = (status: string) => {
|
|
|
+ const colorMap: Record<string, string> = {
|
|
|
+ 有效: 'success',
|
|
|
+ 已终止: 'info',
|
|
|
+ }
|
|
|
+ return colorMap[status] || ''
|
|
|
}
|
|
|
|
|
|
onMounted(() => {
|
|
|
@@ -894,8 +1070,14 @@ onMounted(() => {
|
|
|
<el-form-item label="房间名称" required>
|
|
|
<el-input v-model="houseForm.houseName" placeholder="请输入房间名称" />
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="状态" required>
|
|
|
- <el-select v-model="houseForm.status" placeholder="请选择状态" class="w-full">
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-select
|
|
|
+ v-model="houseForm.status"
|
|
|
+ placeholder="请选择状态"
|
|
|
+ class="w-full"
|
|
|
+ default-first-option
|
|
|
+ disabled
|
|
|
+ >
|
|
|
<el-option label="空闲" value="空闲" />
|
|
|
<el-option label="已租" value="已租" />
|
|
|
</el-select>
|
|
|
@@ -924,7 +1106,7 @@ onMounted(() => {
|
|
|
<el-dialog
|
|
|
v-model="detailDialogVisible"
|
|
|
:title="`${currentHouseName} - 详细信息管理`"
|
|
|
- width="1000px"
|
|
|
+ width="1200px"
|
|
|
:close-on-click-modal="false"
|
|
|
class="detail-dialog"
|
|
|
>
|
|
|
@@ -1044,6 +1226,192 @@ onMounted(() => {
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-tab-pane>
|
|
|
+
|
|
|
+ <!-- 租户信息标签页 (仅已租房屋显示) -->
|
|
|
+ <el-tab-pane v-if="showTenantAndContractTabs" label="租户信息" name="tenant">
|
|
|
+ <template #label>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <Users class="w-4 h-4" />
|
|
|
+ <span>租户信息</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="p-4" v-loading="tenantLoading">
|
|
|
+ <div v-if="tenantInfo" class="bg-gray-50 rounded-lg p-6">
|
|
|
+ <div class="grid grid-cols-2 gap-6">
|
|
|
+ <div class="space-y-4">
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-20">租户姓名:</span>
|
|
|
+ <span class="text-gray-900">{{ tenantInfo.tenantName }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-20">联系电话:</span>
|
|
|
+ <span class="text-gray-900">{{ tenantInfo.tenantNumber }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-20">身份证号:</span>
|
|
|
+ <span class="text-gray-900">{{ tenantInfo.tenantIdCard }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="space-y-4">
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-20">入住时间:</span>
|
|
|
+ <span class="text-gray-900">{{ tenantInfo.tenantInDate }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-20">租期:</span>
|
|
|
+ <span class="text-gray-900">{{ tenantInfo.tenantTime }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-20">月租金:</span>
|
|
|
+ <span class="text-green-600 font-semibold">¥{{ tenantInfo.tenantRent }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="text-center py-12">
|
|
|
+ <Users class="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
|
|
+ <p class="text-gray-500 text-lg">暂无租户信息</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <!-- 合同信息标签页 (仅已租房屋显示) -->
|
|
|
+ <el-tab-pane v-if="showTenantAndContractTabs" label="合同信息" name="contract">
|
|
|
+ <template #label>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <FileText class="w-4 h-4" />
|
|
|
+ <span>合同信息</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="p-4" v-loading="contractLoading">
|
|
|
+ <div v-if="contractInfo" class="space-y-6">
|
|
|
+ <!-- 合同基本信息 -->
|
|
|
+ <div class="bg-gray-50 rounded-lg p-6">
|
|
|
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">合同基本信息</h3>
|
|
|
+ <div class="grid grid-cols-2 gap-6">
|
|
|
+ <div class="space-y-4">
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-24">合同编号:</span>
|
|
|
+ <span class="text-gray-900">{{ contractInfo.contractNumber }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-24">签约日期:</span>
|
|
|
+ <span class="text-gray-900">{{ contractInfo.contractDate }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-24">合同期限:</span>
|
|
|
+ <span class="text-gray-900">{{ contractInfo.contractTime }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="space-y-4">
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-24">到期日期:</span>
|
|
|
+ <span class="text-gray-900">{{ contractInfo.contractExpirationDate }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-24">押金:</span>
|
|
|
+ <span class="text-green-600 font-semibold"
|
|
|
+ >¥{{ contractInfo.contractDeposit }}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-gray-600 font-medium w-24">合同状态:</span>
|
|
|
+ <el-tag :type="getContractStatusColor(contractInfo.contractStatus)">
|
|
|
+ {{ contractInfo.contractStatus }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 合同文档 -->
|
|
|
+ <div class="bg-gray-50 rounded-lg p-6">
|
|
|
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">合同文档</h3>
|
|
|
+ <div class="space-y-4">
|
|
|
+ <!-- 原合同 -->
|
|
|
+ <div class="flex items-center justify-between p-4 bg-white rounded-lg border">
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <FileText class="w-6 h-6 text-blue-600" />
|
|
|
+ <div>
|
|
|
+ <p class="font-medium text-gray-900">原合同文档</p>
|
|
|
+ <p class="text-sm text-gray-500">原始合同文件</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ @click="previewDocx(contractInfo.originalContractUrl, '原合同文档')"
|
|
|
+ :loading="previewLoading"
|
|
|
+ :icon="Eye"
|
|
|
+ >
|
|
|
+ 预览
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="success"
|
|
|
+ size="small"
|
|
|
+ @click="
|
|
|
+ downloadDocument(contractInfo.originalContractUrl, '原合同文档.docx')
|
|
|
+ "
|
|
|
+ :icon="Download"
|
|
|
+ >
|
|
|
+ 下载
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 签订后合同 -->
|
|
|
+ <div
|
|
|
+ v-if="contractInfo.signContractUrl"
|
|
|
+ class="flex items-center justify-between p-4 bg-white rounded-lg border"
|
|
|
+ >
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <FileText class="w-6 h-6 text-green-600" />
|
|
|
+ <div>
|
|
|
+ <p class="font-medium text-gray-900">签订后合同</p>
|
|
|
+ <p class="text-sm text-gray-500">已签署的合同文件</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ @click="previewDocx(contractInfo.signContractUrl, '签订后合同')"
|
|
|
+ :loading="previewLoading"
|
|
|
+ :icon="Eye"
|
|
|
+ >
|
|
|
+ 预览
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="success"
|
|
|
+ size="small"
|
|
|
+ @click="downloadDocument(contractInfo.signContractUrl, '签订后合同.docx')"
|
|
|
+ :icon="Download"
|
|
|
+ >
|
|
|
+ 下载
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 签订后合同为空的提示 -->
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ class="flex items-center justify-center p-8 bg-white rounded-lg border border-dashed border-gray-300"
|
|
|
+ >
|
|
|
+ <div class="text-center">
|
|
|
+ <FileText class="w-12 h-12 text-gray-400 mx-auto mb-2" />
|
|
|
+ <p class="text-gray-500">签订后合同暂未上传</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="text-center py-12">
|
|
|
+ <FileText class="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
|
|
+ <p class="text-gray-500 text-lg">暂无合同信息</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
</el-tabs>
|
|
|
|
|
|
<template #footer>
|
|
|
@@ -1053,6 +1421,29 @@ onMounted(() => {
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
+ <!-- 合同文档预览对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="contractPreviewDialogVisible"
|
|
|
+ :title="previewTitle"
|
|
|
+ width="60%"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ class="preview-dialog"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-if="previewContent"
|
|
|
+ v-html="previewContent.innerHTML"
|
|
|
+ class="preview-content max-h-[70vh] overflow-y-auto"
|
|
|
+ ></div>
|
|
|
+ <div v-else class="text-center py-12">
|
|
|
+ <p class="text-gray-500">文档加载中...</p>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <div class="flex justify-end">
|
|
|
+ <el-button @click="contractPreviewDialogVisible = false">关闭</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
<!-- 设备新增/编辑对话框 -->
|
|
|
<el-dialog
|
|
|
v-model="deviceDialogVisible"
|
|
|
@@ -1289,4 +1680,46 @@ onMounted(() => {
|
|
|
--el-badge-size: 16px;
|
|
|
--el-badge-padding: 4px;
|
|
|
}
|
|
|
+
|
|
|
+.preview-dialog .el-dialog__body {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-content {
|
|
|
+ background: white;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 20px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-content p {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-content h1,
|
|
|
+.preview-content h2,
|
|
|
+.preview-content h3 {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ margin-top: 24px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-content table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ margin: 16px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-content table th,
|
|
|
+.preview-content table td {
|
|
|
+ border: 1px solid #d1d5db;
|
|
|
+ padding: 8px 12px;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-content table th {
|
|
|
+ background-color: #f9fafb;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
</style>
|