You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在客户端用Vanilla JS渲染Astro组件并实现动态搜索?

解决方案

方案一:用Vanilla JS手动渲染组件(适合快速实现)

1. 提取HTML模板

把Astro组件的HTML结构转成可复用的模板,用<template>标签存放在页面中:

<template id="job-card-template">
  <div class='transition ease-in-out delay-150 flex overflow-hidden border border-zinc-300 rounded-md ring-slate-800  shadow-md duration-300 hover:border-zinc-500 hover:shadow-lg hover:scale-105'>
    <img src="" onerror="this.onerror=null; this.src='/images/joblogo.png'" alt="logo" class=" bg-white w-28 sm:w-32 object-cover object-center">
    <div class="flex flex-col text-[#002838] mx-2 my-2 sm:mx-4 sm:my-4 text-sm w-full">
      <h1 class="font-bold"></h1>
      <div class="text-xs mt-1">
        <h2 class="font-medium mt-1"></h2>
        <p class="mb-1"></p>
        <p></p>
      </div>
      <div class="flex mt-6 items-center">
        <ul class="flex flex-wrap gap-2 text-[8px] sm:text-xs font-medium"></ul>
      </div>
      <div class="apply-buttons"></div>
    </div>
  </div>
</template>

2. 编写渲染函数

实现Vanilla JS函数,接收单个Job数据并生成对应DOM元素:

function renderJobCard(jobData) {
  const template = document.getElementById('job-card-template');
  const card = template.content.cloneNode(true);
  
  // 填充基础信息
  card.querySelector('img').src = jobData.logo;
  card.querySelector('h1').textContent = jobData.title;
  card.querySelector('h2').textContent = jobData.company;
  card.querySelector('.text-xs p:first-of-type').textContent = jobData.type;
  
  // 处理截止日期和close状态
  const deadlineEl = card.querySelector('.text-xs p:last-of-type');
  deadlineEl.textContent = `Apply before: ${jobData.deadline}`;
  if (jobData.close) {
    deadlineEl.classList.add('text-[#ED0000]', 'flex', 'relative');
    // 插入告警元素(提前把Siren组件的SVG转成字符串)
    const alertHTML = `
      <span class="flex">
        <span class="animate-ping mx-[0.68rem] absolute inline-flex h-3 w-3 rounded-full bg-[#ED0000] opacity-75"></span>
        <span class="relative mx-2 -my-1">${sirenSvgString}</span>
      </span>
    `;
    deadlineEl.insertAdjacentHTML('beforeend', alertHTML);
  }
  
  // 渲染技能标签
  const tagsList = card.querySelector('ul');
  jobData.skills.split(',').forEach(tag => {
    const li = document.createElement('li');
    li.className = 'rounded-sm bg-[#002838] text-white text-start px-1 hover:bg-[#00283896] duration-150';
    li.textContent = tag.trim();
    tagsList.appendChild(li);
  });
  
  // 生成申请按钮
  const buttonsContainer = card.querySelector('.apply-buttons');
  if (jobData.link) {
    const linkBtn = document.createElement('button');
    linkBtn.className = 'self-end mt-4 rounded-lg bg-[#0374E2] text-white w-16 sm:w-20 item-center text-md h-6 hover:text-zinc-800 hover:bg-accent duration-150 hover:shadow-md';
    linkBtn.innerHTML = `<a href="${jobData.link}" target="_blank">Apply</a>`;
    buttonsContainer.appendChild(linkBtn);
  }
  if (jobData.email) {
    const emailBtn = document.createElement('button');
    emailBtn.className = 'self-end mt-4 rounded-lg bg-[#0374E2] text-white w-16 sm:w-20 item-center text-md h-6 hover:text-zinc-800 hover:bg-accent duration-150 hover:shadow-md';
    emailBtn.textContent = 'Apply';
    emailBtn.onclick = () => openModal(jobData.email);
    buttonsContainer.appendChild(emailBtn);
  }
  
  return card;
}

// 提前将Siren.astro编译后的SVG内容转为字符串
const sirenSvgString = `<!-- 此处粘贴Siren组件的SVG代码 -->`;

3. 搜索与动态更新逻辑

实现API调用和渲染更新的绑定:

async function fetchJobs(searchQuery = '') {
  const res = await fetch(`/api/jobs?search=${encodeURIComponent(searchQuery)}`);
  return res.json();
}

async function renderJobs(searchQuery) {
  const container = document.getElementById('jobs-container');
  container.innerHTML = '';
  
  const jobs = await fetchJobs(searchQuery);
  jobs.forEach(job => {
    container.appendChild(renderJobCard(job));
  });
}

// 绑定搜索框事件
document.getElementById('search-input').addEventListener('input', (e) => {
  renderJobs(e.target.value);
});

// 页面加载时渲染初始数据
window.addEventListener('load', () => renderJobs());

方案二:封装为Web Component(Astro原生支持)

1. 修改Astro组件为Web Component

调整Astro组件,添加自定义元素声明和属性监听:

---
import Siren from '@components/Icons/Siren.astro';
import type { IJob } from '@util/job.interface';

export interface Props extends IJob{}
const { company, title, email, type, deadline, skills, link, logo, close=false } = Astro.props;
const tags = skills.split(",");
---

<siren-job-card is:inline>
  <div class='transition ease-in-out delay-150 flex overflow-hidden border border-zinc-300 rounded-md ring-slate-800  shadow-md duration-300 hover:border-zinc-500 hover:shadow-lg hover:scale-105'>
    <img src={logo} onerror="this.onerror=null; this.src='/images/joblogo.png'" alt="logo" class=" bg-white w-28 sm:w-32 object-cover object-center">
    <div class="flex flex-col text-[#002838] mx-2 my-2 sm:mx-4 sm:my-4 text-sm w-full">
      <h1 class="font-bold">{title}</h1>
      <div class="text-xs mt-1">
        <h2 class="font-medium mt-1">{company}</h2>
        <p class="mb-1">{type}</p>
        <p class={close && 'text-[#ED0000] flex relative'}>Apply before: {deadline} {close && 
          <span class="flex">
            <span class="animate-ping mx-[0.68rem] absolute inline-flex h-3 w-3 rounded-full bg-[#ED0000] opacity-75"></span>
            <span class="relative mx-2 -my-1"><Siren/></span>
          </span>
        }
        </p>
      </div>
      <div class="flex mt-6 items-center">
        <ul class="flex flex-wrap gap-2 text-[8px] sm:text-xs font-medium">
          {tags.map(tag=>{
            return <li class="rounded-sm bg-[#002838] text-white text-start px-1 hover:bg-[#00283896] duration-150">{tag}</li>
          })}
        </ul>
      </div>
      {link && <button type="button" class="self-end mt-4 rounded-lg bg-[#0374E2] text-white w-16 sm:w-20 item-center text-md h-6 hover:text-zinc-800 hover:bg-accent duration-150 hover:shadow-md">
        <a href={link} target="_blank">Apply</a>
      </button>}
      {email && <button type="button" onclick={`openModal("${email}")`} class="self-end mt-4 rounded-lg bg-[#0374E2] text-white w-16 sm:w-20 item-center text-md h-6 hover:text-zinc-800 hover:bg-accent duration-150 hover:shadow-md">
        Apply
      </button>}
    </div>
  </div>
</siren-job-card>

<script>
class SirenJobCard extends HTMLElement {
  static get observedAttributes() {
    return ['company', 'title', 'email', 'type', 'deadline', 'skills', 'link', 'logo', 'close'];
  }

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = this.innerHTML;
  }

  attributeChangedCallback(name, oldVal, newVal) {
    this.updateContent();
  }

  updateContent() {
    // 动态更新技能标签
    const skills = this.getAttribute('skills');
    if (skills) {
      const tagsList = this.shadowRoot.querySelector('ul');
      tagsList.innerHTML = '';
      skills.split(',').forEach(tag => {
        const li = document.createElement('li');
        li.className = 'rounded-sm bg-[#002838] text-white text-start px-1 hover:bg-[#00283896] duration-150';
        li.textContent = tag.trim();
        tagsList.appendChild(li);
      });
    }
  }
}

customElements.define('siren-job-card', SirenJobCard);
</script>

2. 客户端传递Props并渲染

通过HTML属性或JS动态设置组件属性:

<!-- 直接用HTML属性传递数据 -->
<siren-job-card 
  company="Example Corp"
  title="Frontend Developer"
  email="jobs@example.com"
  type="Full-time"
  deadline="2024-12-31"
  skills="React, Astro, Tailwind"
  link="https://example.com/apply"
  logo="/images/corp-logo.png"
  close="true"
></siren-job-card>

<!-- JS动态创建组件 -->
<script>
async function renderJobs(searchQuery) {
  const container = document.getElementById('jobs-container');
  container.innerHTML = '';
  
  const jobs = await fetch(`/api/jobs?search=${encodeURIComponent(searchQuery)}`);
  const jobsData = await jobs.json();
  
  jobsData.forEach(job => {
    const card = document.createElement('siren-job-card');
    card.setAttribute('company', job.company);
    card.setAttribute('title', job.title);
    card.setAttribute('email', job.email);
    card.setAttribute('type', job.type);
    card.setAttribute('deadline', job.deadline);
    card.setAttribute('skills', job.skills);
    card.setAttribute('link', job.link);
    card.setAttribute('logo', job.logo);
    if (job.close) card.setAttribute('close', 'true');
    
    container.appendChild(card);
  });
}
</script>

3. 搜索逻辑同方案一

绑定搜索框事件,调用API后动态创建自定义元素即可。


内容的提问来源于stack exchange,提问作者Mahmoud Galal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 23:09:26