Vue模板化配置-解析json数据


实现给定模板后自动加载显示数据
主要实现功能:
1.图片显示全路径,鼠标悬停后显示图片
2.附件显示附件名称,点击附件名称即可下载附件
3.列表可以支持分页显示,设定列表最大显示行数,超过行数则分页显示

1.根据页面要求设计模板

 {
  "organization": {
    "__description": "机构信息",
    "__type": "object",
    "baseInfo": {
      "__description": "基本信息",
      "__type": "object",
      "name": "名称",
      "logo": "头像",
      "abbreviation": "缩写",
      "type": "类型",
      "registrationNumber": "注册编号",
      "taxId": "税号",
      "foundingDate": "成立日期",
      "missionStatement": "使命宣言",
      "description": {
        "__description": "描述",
        "__showWay": "textField"
      },
      "remark": {
        "__description": "简介",
        "__showWay": "textField"
      }
    },
    "address": {
      "__description": "地址",
      "__type": "object",
      "street": "街道",
      "city": "城市",
      "state": "州/省",
      "postalCode": "邮政编码",
      "country": "国家"
    },
    "contact": {
      "__description": "联系方式",
      "__type": "object",
      "phone": "电话",
      "email": "邮箱",
      "website": "网站",
      "contract": "合规报告"
    },
    "socialMediaLinks": {
      "__description": "社交媒体链接",
      "__type": "object",
      "facebook": "Facebook",
      "twitter": "Twitter",
      "linkedin": "LinkedIn",
      "instagram": "Instagram"
    },
    "members": {
      "__description": "成员列表",
      "__type": "list",
      "0": {
        "name": "姓名",
        "position": "职位",
        "phone": "电话",
        "email": "邮箱",
        "bio": "简介"
      }
    },
    "paymentRecords": {
      "__description": "缴费记录",
      "__type": "list",
      "0": {
        "payType": "缴费类型",
        "payMoney": "缴费金额",
        "payStatus": "缴费状态",
        "payTime": "缴费时间"
      }
    }
  }
}

2.编写代码实现

<template>
  <div class="container">
    <!-- 增加对 data 和 templateData 的检查 -->
    <div v-if="templateData && data && data.organization">
      <template v-if="templateData.__type === 'object'">
        <h2 class="title">{{ templateData.__description }}</h2>
        <div class="details">
          <div v-for="(value, key) in templateData" :key="key" class="section">
            <template v-if="!['__description', '__type'].includes(key)">
              <!-- 嵌套对象 -->
              <div v-if="value.__type === 'object'">
                <h3 class="section-title">{{ value.__description }}</h3>
                <div class="nested-fields">
                  <div
                    v-for="(nestedValue, nestedKey) in value"
                    :key="nestedKey"
                    class="field"
                    :class="{ 'full-width': nestedValue.__showWay === 'textField' }"
                  >
                    <template v-if="!['__description', '__type'].includes(nestedKey)">
                      <!-- 文本域 -->
                      <template v-if="nestedValue.__showWay === 'textField'">
                        <strong class="field-label">{{ nestedValue.__description }}:</strong>
                        <textarea
                          class="field-value textarea"
                          :value="data.organization[key][nestedKey]"
                          readonly
                        ></textarea>
                      </template>
                      <!-- 图片字段 -->
                      <template v-else-if="isImage(data.organization[key][nestedKey])">
                        <strong class="field-label">{{ nestedValue }}:</strong>
                        <div
                          class="image-container"
                          @mouseover="showImagePreview(data.organization[key][nestedKey])"
                          @mouseleave="hideImagePreview"
                        >
                          <span class="field-value">{{ data.organization[key][nestedKey] }}</span>
                          <div class="image-preview" v-if="hoveredImage === data.organization[key][nestedKey]">
                            <img :src="data.organization[key][nestedKey]" alt="Image Preview" class="preview-image" />
                          </div>
                        </div>
                      </template>
                      <!-- 附件字段 -->
                      <template v-else-if="isAttachment(data.organization[key][nestedKey])">
                        <strong class="field-label">{{ nestedValue }}:</strong>
                        <div class="attachment-container">
                          <a
                            class="attachment-link"
                            :href="data.organization[key][nestedKey]"
                            @click.prevent="downloadFile(data.organization[key][nestedKey])"
                          >
                            {{ getFileName(data.organization[key][nestedKey]) }}
                          </a>
                        </div>
                      </template>
                      <!-- 普通字段 -->
                      <template v-else>
                        <strong class="field-label">{{ nestedValue }}:</strong>
                        <span class="field-value">{{ data.organization[key][nestedKey] }}</span>
                      </template>
                    </template>
                  </div>
                </div>
              </div>
              <!-- 列表 -->
              <div v-else-if="value.__type === 'list'">
                <h3 class="section-title">{{ value.__description }}</h3>
                <table class="data-table">
                  <thead>
                    <tr>
                      <th v-for="(headerValue, headerKey) in value[0]" :key="headerKey">
                        {{ headerValue }}
                      </th>
                    </tr>
                  </thead>
                  <tbody>
                    <tr v-for="(item, index) in paginatedData(data.organization[key], key)" :key="index">
                      <td v-for="(headerValue, headerKey) in value[0]" :key="headerKey">
                        {{ item[headerKey] }}
                      </td>
                    </tr>
                  </tbody>
                </table>
                <!-- 分页控件 -->
                <div class="pagination" v-if="data.organization[key].length > itemsPerPage">
                  <button
                    v-for="page in Math.ceil(data.organization[key].length / itemsPerPage)"
                    :key="page"
                    @click="changePage(page, key)"
                    :class="{ active: paginationState[key] === page }"
                  >
                    {{ page }}
                  </button>
                </div>
              </div>
              <!-- 普通字段 -->
              <template v-else>
                <div class="field">
                  <strong class="field-label">{{ value }}:</strong>
                  <span class="field-value">{{ data.organization[key] }}</span>
                </div>
              </template>
            </template>
          </div>
        </div>
      </template>
    </div>
    <div v-else class="loading">
      <p>加载中...</p>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";

// 模板集合
const templates = {
  "organization": {
    "__description": "机构信息",
    "__type": "object",
    "baseInfo": {
      "__description": "基本信息",
      "__type": "object",
      "name": "名称",
      "logo": "头像",
      "abbreviation": "缩写",
      "type": "类型",
      "registrationNumber": "注册编号",
      "taxId": "税号",
      "foundingDate": "成立日期",
      "missionStatement": "使命宣言",
      "description": {
        "__description": "描述",
        "__showWay": "textField"
      },
      "remark": {
        "__description": "简介",
        "__showWay": "textField"
      }
    },
    "address": {
      "__description": "地址",
      "__type": "object",
      "street": "街道",
      "city": "城市",
      "state": "州/省",
      "postalCode": "邮政编码",
      "country": "国家"
    },
    "contact": {
      "__description": "联系方式",
      "__type": "object",
      "phone": "电话",
      "email": "邮箱",
      "website": "网站",
      "contract": "合规报告"
    },
    "socialMediaLinks": {
      "__description": "社交媒体链接",
      "__type": "object",
      "facebook": "Facebook",
      "twitter": "Twitter",
      "linkedin": "LinkedIn",
      "instagram": "Instagram"
    },
    "members": {
      "__description": "成员列表",
      "__type": "list",
      "0": {
        "name": "姓名",
        "position": "职位",
        "phone": "电话",
        "email": "邮箱",
        "bio": "简介"
      }
    },
    "paymentRecords": {
      "__description": "缴费记录",
      "__type": "list",
      "0": {
        "payType": "缴费类型",
        "payMoney": "缴费金额",
        "payStatus": "缴费状态",
        "payTime": "缴费时间"
      }
    }
  }
};

// 当前模板数据
const templateData = ref(null);
// 当前数据
const data = ref(null);
// 模板名称
const templateName = ref("organization");
// 当前悬停的图片 URL
const hoveredImage = ref(null);
// 分页状态,存储每个列表的当前页码
const paginationState = ref({});
// 每页显示的条数
const itemsPerPage = 5;

// 判断是否为图片 URL
const isImage = (url) => {
  return url && /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
};

// 判断是否为附件
const isAttachment = (url) => {
  return url && /\.(pdf|doc|docx|xls|xlsx|zip|rar)$/i.test(url);
};

// 显示图片预览
const showImagePreview = (imageUrl) => {
  hoveredImage.value = imageUrl;
};

// 隐藏图片预览
const hideImagePreview = () => {
  hoveredImage.value = null;
};

// 从 URL 中提取文件名
const getFileName = (url) => {
  return url.split("/").pop(); // 获取 URL 的最后一部分作为文件名
};

// 下载文件
const downloadFile = (url) => {
  const link = document.createElement("a");
  link.href = url;
  link.download = getFileName(url); // 使用提取的文件名
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
};

// 获取当前列表的分页数据
const paginatedData = (list, listKey) => {
  const currentPage = paginationState.value[listKey] || 1;
  const start = (currentPage - 1) * itemsPerPage;
  const end = start + itemsPerPage;
  return list.slice(start, end);
};

// 切换页码
const changePage = (page, listKey) => {
  paginationState.value[listKey] = page;
};

// 根据模板名称加载模板和数据
const loadTemplateAndData = (name) => {
  // 加载模板
  templateData.value = templates[name];

  // 模拟加载数据
  if (name === "organization") {
    data.value = {
      "organization": {
        "baseInfo": {
          "name": "云基华海信息技术有限公司",
          "logo": "http://10.168.3.13:30013/img/icon1.0a93c87a.png",
          "abbreviation": "云基华海",
          "type": "科技公司",
          "registrationNumber": "123456789",
          "taxId": "987654321",
          "foundingDate": "2010-05-20",
          "missionStatement": "用科技赋能未来",
          "description": "一家专注于大数据和人工智能技术的高科技公司。",
          "remark": "话说秦钟既死,宝玉痛哭不止,李贵等好容易劝解半日方住,归时还带余哀。贾母帮了几十两银子外,又另备奠仪,宝玉去吊祭。七日后便送殡掩埋了,别无记述。只有宝玉,日日感悼,思念不已,然亦无可如何了,又不知过了几时才罢。"
        },
        "address": {
          "street": "科技大道123号",
          "city": "西安",
          "state": "陕西省",
          "postalCode": "710000",
          "country": "中国"
        },
        "contact": {
          "phone": "+86 029 1234 5678",
          "email": "info@cloudbase.com",
          "website": "https://www.cloudbase.com",
          "contract": "http://10.168.2.12:9000/nanshan/scenemanage/2025-03-06/b59ba1adbea3458383b683e12eb8e617.pdf"
        },
        "socialMediaLinks": {
          "facebook": "https://facebook.com/cloudbase",
          "twitter": "https://twitter.com/cloudbase",
          "linkedin": "https://linkedin.com/company/cloudbase",
          "instagram": "https://instagram.com/cloudbase"
        },
        "members": [{
            "name": "张三",
            "position": "首席执行官",
            "phone": "+86 138 0013 8000",
            "email": "zhangsan@cloudbase.com",
            "bio": "拥有10年科技行业经验,专注于企业数字化转型。"
          },
          {
            "name": "李四",
            "position": "首席技术官",
            "phone": "+86 138 0013 8001",
            "email": "lisi@cloudbase.com",
            "bio": "大数据和人工智能领域的专家,主导多个国家级项目。"
          },
          {
            "name": "王五",
            "position": "市场总监",
            "phone": "+86 138 0013 8002",
            "email": "wangwu@cloudbase.com",
            "bio": "负责公司品牌推广和市场战略规划。"
          }
        ],
        "paymentRecords": [{
            "payType": "购买套餐",
            "payMoney": "10.00元",
            "payStatus": "成功",
            "payTime": "2025-03-06 16:05:17.055"
          },
          {
            "payType": "购买资源",
            "payMoney": "1000.00元",
            "payStatus": "成功",
            "payTime": "2025-03-06 16:05:17.055"
          },
          {
            "payType": "发展会员",
            "payMoney": "3000.00元",
            "payStatus": "成功",
            "payTime": "2025-03-06 16:05:17.055"
          },
          {
            "payType": "商务开销",
            "payMoney": "5000.00元",
            "payStatus": "成功",
            "payTime": "2025-03-06 16:05:17.055"
          },
          {
            "payType": "员工团建",
            "payMoney": "8000.00元",
            "payStatus": "成功",
            "payTime": "2025-03-06 16:05:17.055"
          },
          {
            "payType": "工资发放",
            "payMoney": "5000.00元",
            "payStatus": "成功",
            "payTime": "2025-03-06 16:05:17.055"
          },
          {
            "payType": "生活开销",
            "payMoney": "3000.00元",
            "payStatus": "成功",
            "payTime": "2025-03-06 16:05:17.055"
          },
          {
            "payType": "日常采买",
            "payMoney": "10000.00元",
            "payStatus": "成功",
            "payTime": "2025-03-06 16:05:17.055"
          }
        ]
      }
    };
  }
};

// 初始化加载模板和数据
onMounted(() => {
  loadTemplateAndData(templateName.value);
  // 初始化分页状态
  if (data.value && data.value.organization) {
    Object.keys(data.value.organization).forEach((key) => {
      if (templateData.value[key]?.__type === "list") {
        paginationState.value[key] = 1; // 默认选中第一页
      }
    });
  }
});
</script>
<style scoped>
.container {
  max-width: 1500px;
  margin: 0 auto;
  padding: 20px;
  font-family: Arial, sans-serif;
}

.title {
  font-size: 24px;
  color: #333;
  margin-bottom: 20px;
}

.details {
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.section {
  margin-bottom: 20px;
}

.section-title {
  font-size: 20px;
  color: #444;
  margin-bottom: 15px;
  border-bottom: 2px solid #eee;
  padding-bottom: 10px;
}

.nested-fields {
  padding-left: 20px;
  display: flex;
  flex-wrap: wrap;
}

.field {
  width: 50%; /* 每行显示两个字段 */
  margin-bottom: 10px;
  display: flex;
  align-items: flex-start; /* 对齐方式调整为顶部对齐 */
}

.field.full-width {
  width: 100%; /* 文本域字段独占一行 */
}

.field-label {
  color: #555;
  font-weight: bold;
  margin-right: 10px;
}

.field-value {
  color: #333;
  flex: 1; /* 占据剩余空间 */
}

.textarea {
  width: 100%;
  height: 100px; /* 文本域高度 */
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  resize: vertical; /* 允许垂直调整大小 */
  background-color: #f9f9f9; /* 只读背景色 */
  font-family: inherit;
  font-size: 14px;
}

.image-container {
  position: relative;
  display: inline-block;
}

.image-preview {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 10;
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 5px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.preview-image {
  max-width: 200px;
  max-height: 200px;
  display: block;
}

.attachment-container {
  display: flex;
  align-items: center;
  gap: 10px;
}

.attachment-link {
  color: #007bff; /* 附件链接颜色 */
  text-decoration: none;
  cursor: pointer;
  font-weight: bold;
}

.attachment-link:hover {
  text-decoration: underline; /* 悬停时添加下划线 */
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 20px;
}

.data-table th,
.data-table td {
  border: 1px solid #ddd;
  padding: 8px;
  text-align: left;
}

.data-table th {
  background-color: #f4f4f4;
  font-weight: bold;
}

.data-table tr:nth-child(even) {
  background-color: #f9f9f9;
}

.data-table tr:hover {
  background-color: #f1f1f1;
}

.loading {
  text-align: center;
  font-size: 18px;
  color: #666;
  margin-top: 50px;
}
.pagination {
  margin-top: 20px;
  display: flex;
  justify-content: center;
  gap: 5px;
}

.pagination button {
  padding: 5px 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  background-color: #fff;
  cursor: pointer;
  transition: background-color 0.3s;
}

.pagination button:hover {
  background-color: #f1f1f1;
}

.pagination button.active {
  background-color: #007bff;
  color: #fff;
  border-color: #007bff;
}
</style>

3.展示效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值