记录利用Cursor快速实现首页数据大屏

目录

1、大屏主页面

src/views/dashboard/DataScreenView.vue

[2、通用面板(40px 标头)](#2、通用面板(40px 标头))

src/components/dashboard/DashboardPanel.vue

3、中间光感联动区

src/components/dashboard/CenterLightVisual.vue

4、实时时钟

src/composables/useDashboardClock.ts

[5、路由 /data-screen](#5、路由 /data-screen)

src/router/index.ts

6、首页增加入口卡片

src/views/home/HomeView.vue


需求:

首页数据大屏需求:

页面布局:左上角为 logo,上方中间展示一个大的标题(内容大概为保险车险类的),右上角显示时间(时间、星期几、年月日)

中间主要区域显示一辆汽车和一栋大楼,之间光线互相牵连,有流动光感。

左下区域展示两个 Echarts 图表,一个柱状图一个饼状图,

右侧区域上下展示两个数据列表

左下区域和右侧区域都是上下结构布局,上方标头大概60px 高,下方根据屏幕看着给分配高度吧。根据以上图片和需求说明,帮我在此项目中新生成一个页面用于展示和测试。

利用 Cursor 实现起来会很快,我们的角色就跟 产品 / 需求 或者 项目经理似的,只需要在 Cursor Agents 窗口内提任务需求就可以了,它就能够快速理解需求并自动帮你实现,话不多说,先看下成果~


接下来看一下 Cursor 是如何一步一步实现的吧~

1、正在探索项目结构与技术栈,以便创建符合需求的数据大屏页面。

2、正在安装 ECharts 并创建数据大屏页面及相关组件。

bash 复制代码
$ cd /Users/jimmy/Desktop/PROJECT/StarCollab && npm install echarts

1、大屏主页面

src/views/dashboard/DataScreenView.vue

javascript 复制代码
<template>
  <div class="data-screen">
    <header class="data-screen__header">
      <div class="data-screen__logo">
        <el-icon :size="36" color="#00d4ff"><Star /></el-icon>
        <span class="data-screen__logo-text">星盘保险</span>
      </div>
      <h1 class="data-screen__title">车险智能运营数据大屏</h1>
      <div class="data-screen__clock">
        <span class="data-screen__time">{{ timeText }}</span>
        <span class="data-screen__weekday">{{ weekdayText }}</span>
        <span class="data-screen__date">{{ dateText }}</span>
      </div>
    </header>

    <div class="data-screen__body">
      <div class="data-screen__main">
        <section class="data-screen__center">
          <CenterLightVisual />
        </section>
        <div class="data-screen__charts">
          <DashboardPanel title="月度保费与赔付" subtitle="单位:万元">
            <div ref="barChartRef" class="chart-box" />
          </DashboardPanel>
          <DashboardPanel title="险种结构占比">
            <div ref="pieChartRef" class="chart-box" />
          </DashboardPanel>
        </div>
      </div>

      <aside class="data-screen__sidebar">
        <DashboardPanel title="今日报案动态">
          <ul class="data-list">
            <li v-for="item in claimList" :key="item.id" class="data-list__row">
              <span class="data-list__plate">{{ item.plate }}</span>
              <span class="data-list__type">{{ item.type }}</span>
              <span class="data-list__status" :class="`is-${item.status}`">{{ item.statusLabel }}</span>
              <span class="data-list__time">{{ item.time }}</span>
            </li>
          </ul>
        </DashboardPanel>
        <DashboardPanel title="渠道业绩排行">
          <ul class="data-list data-list--rank">
            <li v-for="(item, index) in rankList" :key="item.name" class="data-list__row">
              <span class="data-list__rank" :class="{ 'is-top': index < 3 }">{{ index + 1 }}</span>
              <span class="data-list__name">{{ item.name }}</span>
              <span class="data-list__value">{{ item.premium }} 万</span>
              <span class="data-list__rate">{{ item.rate }}%</span>
            </li>
          </ul>
        </DashboardPanel>
      </aside>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import * as echarts from 'echarts'
import { Star } from '@element-plus/icons-vue'
import DashboardPanel from '@/components/dashboard/DashboardPanel.vue'
import CenterLightVisual from '@/components/dashboard/CenterLightVisual.vue'
import { useDashboardClock } from '@/composables/useDashboardClock'

const { timeText, weekdayText, dateText } = useDashboardClock()

const barChartRef = ref<HTMLElement | null>(null)
const pieChartRef = ref<HTMLElement | null>(null)

let barChart: echarts.ECharts | null = null
let pieChart: echarts.ECharts | null = null

const claimList = [
  { id: 1, plate: '沪A·8K21X', type: '剐蹭', status: 'processing', statusLabel: '处理中', time: '20:41' },
  { id: 2, plate: '苏E·3M902', type: '单方事故', status: 'done', statusLabel: '已结案', time: '19:58' },
  { id: 3, plate: '浙B·7P118', type: '涉水', status: 'processing', statusLabel: '定损中', time: '19:22' },
  { id: 4, plate: '粤S·2N556', type: '追尾', status: 'pending', statusLabel: '待派工', time: '18:47' },
  { id: 5, plate: '京N·9D003', type: '玻璃险', status: 'done', statusLabel: '已赔付', time: '17:30' },
  { id: 6, plate: '川A·5H778', type: '盗抢险', status: 'processing', statusLabel: '审核中', time: '16:15' },
]

const rankList = [
  { name: '华东直营中心', premium: 1286, rate: 18.2 },
  { name: '华南代理联盟', premium: 1104, rate: 15.6 },
  { name: '华北车商渠道', premium: 982, rate: 12.4 },
  { name: '西南电销团队', premium: 756, rate: 9.8 },
  { name: '华中续保专席', premium: 688, rate: 8.1 },
  { name: '东北县域网点', premium: 521, rate: 6.3 },
]

const chartTheme = {
  textStyle: { color: '#9ec8ff' },
  axisLine: { lineStyle: { color: 'rgba(0, 180, 255, 0.35)' } },
  splitLine: { lineStyle: { color: 'rgba(0, 120, 200, 0.15)' } },
}

function initBarChart(): void {
  if (!barChartRef.value) return
  barChart = echarts.init(barChartRef.value)
  barChart.setOption({
    grid: { left: 48, right: 24, top: 36, bottom: 32 },
    tooltip: { trigger: 'axis' },
    legend: {
      top: 4,
      textStyle: { color: '#9ec8ff' },
      data: ['保费', '赔付'],
    },
    xAxis: {
      type: 'category',
      data: ['1月', '2月', '3月', '4月', '5月', '6月'],
      axisLabel: chartTheme.textStyle,
      axisLine: chartTheme.axisLine,
    },
    yAxis: {
      type: 'value',
      axisLabel: chartTheme.textStyle,
      splitLine: chartTheme.splitLine,
    },
    series: [
      {
        name: '保费',
        type: 'bar',
        barWidth: 18,
        itemStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: '#00d4ff' },
            { offset: 1, color: '#0066aa' },
          ]),
        },
        data: [820, 932, 901, 1034, 1090, 1180],
      },
      {
        name: '赔付',
        type: 'bar',
        barWidth: 18,
        itemStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: '#ffb347' },
            { offset: 1, color: '#cc6600' },
          ]),
        },
        data: [420, 512, 488, 560, 602, 640],
      },
    ],
  })
}

function initPieChart(): void {
  if (!pieChartRef.value) return
  pieChart = echarts.init(pieChartRef.value)
  pieChart.setOption({
    tooltip: { trigger: 'item' },
    legend: {
      orient: 'vertical',
      right: 8,
      top: 'center',
      textStyle: { color: '#9ec8ff', fontSize: 11 },
    },
    series: [
      {
        type: 'pie',
        radius: ['42%', '68%'],
        center: ['38%', '50%'],
        label: { color: '#cfe6ff', fontSize: 11 },
        data: [
          { value: 42, name: '交强险' },
          { value: 28, name: '商业三者' },
          { value: 15, name: '车损险' },
          { value: 10, name: '驾乘意外险' },
          { value: 5, name: '其他附加' },
        ],
        itemStyle: {
          borderColor: '#041428',
          borderWidth: 2,
        },
        color: ['#00d4ff', '#3d8fff', '#ffb347', '#67e8a3', '#b388ff'],
      },
    ],
  })
}

function handleResize(): void {
  barChart?.resize()
  pieChart?.resize()
}

onMounted(() => {
  initBarChart()
  initPieChart()
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
  barChart?.dispose()
  pieChart?.dispose()
})
</script>

<style scoped>
.data-screen {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: #020b18 radial-gradient(ellipse at 50% 0%, #0a2848 0%, #020b18 55%);
  color: #e8f4ff;
}

.data-screen__header {
  flex-shrink: 0;
  height: 72px;
  display: grid;
  grid-template-columns: 220px 1fr 220px;
  align-items: center;
  padding: 0 28px;
  border-bottom: 1px solid rgba(0, 180, 255, 0.25);
  background: linear-gradient(180deg, rgba(0, 60, 120, 0.35), transparent);
}

.data-screen__logo {
  display: flex;
  align-items: center;
  gap: 10px;
}

.data-screen__logo-text {
  font-size: 18px;
  font-weight: 600;
  color: #7ec8ff;
  letter-spacing: 2px;
}

.data-screen__title {
  text-align: center;
  font-size: clamp(22px, 2.4vw, 36px);
  font-weight: 700;
  letter-spacing: 6px;
  background: linear-gradient(90deg, #7ec8ff, #00e5ff, #7ec8ff);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-shadow: 0 0 30px rgba(0, 200, 255, 0.35);
}

.data-screen__clock {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
}

.data-screen__time {
  font-size: 28px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #00e5ff;
}

.data-screen__weekday,
.data-screen__date {
  font-size: 13px;
  color: rgba(160, 210, 255, 0.85);
}

.data-screen__body {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr minmax(280px, 26vw);
  gap: 14px;
  padding: 14px;
}

.data-screen__main {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.data-screen__center {
  flex: 1.15;
  min-height: 0;
  border: 1px solid rgba(0, 180, 255, 0.2);
  background: rgba(4, 20, 45, 0.5);
  border-radius: 4px;
  padding: 8px;
}

.data-screen__charts {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.data-screen__sidebar {
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.data-screen__sidebar > :deep(.dashboard-panel) {
  flex: 1;
  min-height: 0;
}

.chart-box {
  width: 100%;
  height: 100%;
  min-height: 160px;
}

.data-list {
  list-style: none;
  height: 100%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-right: 4px;
}

.data-list__row {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr 0.7fr 0.5fr;
  gap: 8px;
  align-items: center;
  padding: 10px 12px;
  font-size: 13px;
  background: rgba(0, 80, 160, 0.12);
  border-left: 3px solid rgba(0, 200, 255, 0.45);
  border-radius: 2px;
}

.data-list--rank .data-list__row {
  grid-template-columns: 36px 1fr 72px 52px;
}

.data-list__plate,
.data-list__name {
  color: #d8ecff;
  font-weight: 500;
}

.data-list__type,
.data-list__time,
.data-list__value {
  color: rgba(160, 210, 255, 0.8);
}

.data-list__status.is-processing {
  color: #ffb347;
}

.data-list__status.is-done {
  color: #67e8a3;
}

.data-list__status.is-pending {
  color: #9ec8ff;
}

.data-list__rank {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 4px;
  background: rgba(0, 100, 180, 0.35);
  color: #9ec8ff;
  font-weight: 600;
}

.data-list__rank.is-top {
  background: linear-gradient(135deg, #ffb347, #ff6b35);
  color: #1a0a00;
}

.data-list__rate {
  text-align: right;
  color: #00e5ff;
}

.data-list::-webkit-scrollbar {
  width: 4px;
}

.data-list::-webkit-scrollbar-thumb {
  background: rgba(0, 180, 255, 0.35);
  border-radius: 2px;
}
</style>
javascript 复制代码
import { onMounted, onUnmounted, ref } from 'vue'

const WEEKDAY_LABELS = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']

/** 数据大屏右上角实时时钟 */
export function useDashboardClock() {
  const timeText = ref('')
  const weekdayText = ref('')
  const dateText = ref('')

  function pad(n: number): string {
    return String(n).padStart(2, '0')
  }

  function tick(): void {
    const now = new Date()
    timeText.value = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
    weekdayText.value = WEEKDAY_LABELS[now.getDay()]
    dateText.value = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`
  }

  let timer: ReturnType<typeof setInterval> | undefined

  onMounted(() => {
    tick()
    timer = setInterval(tick, 1000)
  })

  onUnmounted(() => {
    if (timer) clearInterval(timer)
  })

  return { timeText, weekdayText, dateText }
}

2、通用面板(40px 标头)

src/components/dashboard/DashboardPanel.vue

javascript 复制代码
<template>
  <section class="dashboard-panel">
    <header class="dashboard-panel__header">
      <span class="dashboard-panel__title">{{ title }}</span>
      <span v-if="subtitle" class="dashboard-panel__subtitle">{{ subtitle }}</span>
    </header>
    <div class="dashboard-panel__body">
      <slot />
    </div>
  </section>
</template>

<script setup lang="ts">
defineProps<{
  title: string
  subtitle?: string
}>()
</script>

<style scoped>
.dashboard-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  border: 1px solid rgba(0, 180, 255, 0.25);
  background: linear-gradient(180deg, rgba(6, 30, 60, 0.85) 0%, rgba(4, 18, 40, 0.92) 100%);
  box-shadow: inset 0 0 24px rgba(0, 120, 255, 0.08);
}

.dashboard-panel__header {
  flex-shrink: 0;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  border-bottom: 1px solid rgba(0, 180, 255, 0.2);
  background: linear-gradient(90deg, rgba(0, 100, 200, 0.15), transparent 60%, rgba(0, 100, 200, 0.15));
}

.dashboard-panel__title {
  font-size: 18px;
  font-weight: 600;
  color: #e8f4ff;
  letter-spacing: 2px;
}

.dashboard-panel__subtitle {
  font-size: 12px;
  color: rgba(160, 210, 255, 0.65);
}

.dashboard-panel__body {
  flex: 1;
  min-height: 0;
  padding: 12px;
  overflow: hidden;
}
</style>

3、中间光感联动区

src/components/dashboard/CenterLightVisual.vue

javascript 复制代码
<template>
  <div class="center-visual">
    <svg
      class="center-visual__svg"
      viewBox="0 0 1000 420"
      preserveAspectRatio="xMidYMid meet"
      aria-hidden="true"
    >
      <defs>
        <linearGradient id="carBody" x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0%" stop-color="#1a6bff" />
          <stop offset="100%" stop-color="#0d3d8c" />
        </linearGradient>
        <linearGradient id="buildingBody" x1="0%" y1="100%" x2="0%" y2="0%">
          <stop offset="0%" stop-color="#0a2848" />
          <stop offset="100%" stop-color="#1e5a9a" />
        </linearGradient>
        <linearGradient id="beamGrad" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="200" y2="0">
          <stop offset="0%" stop-color="transparent" />
          <stop offset="35%" stop-color="#00e5ff" />
          <stop offset="65%" stop-color="#4d9fff" />
          <stop offset="100%" stop-color="transparent" />
          <animate
            attributeName="x1"
            values="-200;800;-200"
            dur="3s"
            repeatCount="indefinite"
          />
          <animate
            attributeName="x2"
            values="0;1000;0"
            dur="3s"
            repeatCount="indefinite"
          />
        </linearGradient>
        <filter id="glow">
          <feGaussianBlur stdDeviation="3" result="blur" />
          <feMerge>
            <feMergeNode in="blur" />
            <feMergeNode in="SourceGraphic" />
          </feMerge>
        </filter>
      </defs>

      <!-- 光感连线 -->
      <g class="light-beams" filter="url(#glow)">
        <path
          class="beam beam--1"
          d="M 280 200 Q 500 80 720 140"
          fill="none"
          stroke="url(#beamGrad)"
          stroke-width="3"
        />
        <path
          class="beam beam--2"
          d="M 300 240 Q 500 320 700 220"
          fill="none"
          stroke="url(#beamGrad)"
          stroke-width="2.5"
        />
        <path
          class="beam beam--3"
          d="M 260 260 Q 500 200 740 280"
          fill="none"
          stroke="rgba(0, 229, 255, 0.35)"
          stroke-width="1.5"
        />
        <circle class="pulse pulse--1" r="5" fill="#00e5ff">
          <animateMotion dur="4s" repeatCount="indefinite" path="M 280 200 Q 500 80 720 140" />
        </circle>
        <circle class="pulse pulse--2" r="4" fill="#6eb6ff">
          <animateMotion dur="3.2s" repeatCount="indefinite" path="M 300 240 Q 500 320 700 220" />
        </circle>
        <circle class="pulse pulse--3" r="3" fill="#00e5ff">
          <animateMotion
            dur="5s"
            repeatCount="indefinite"
            path="M 260 260 Q 500 200 740 280"
          />
        </circle>
      </g>

      <!-- 汽车 -->
      <g class="car" transform="translate(80, 150)">
        <rect x="20" y="70" width="200" height="50" rx="12" fill="url(#carBody)" stroke="#3d9fff" stroke-width="2" />
        <rect x="50" y="35" width="120" height="45" rx="8" fill="#1a4a8a" stroke="#5eb0ff" stroke-width="1.5" />
        <circle cx="60" cy="125" r="22" fill="#0a1e3a" stroke="#00d4ff" stroke-width="3" />
        <circle cx="180" cy="125" r="22" fill="#0a1e3a" stroke="#00d4ff" stroke-width="3" />
        <circle cx="60" cy="125" r="10" fill="#1a3d6e" />
        <circle cx="180" cy="125" r="10" fill="#1a3d6e" />
        <text x="110" y="105" text-anchor="middle" fill="#a8d8ff" font-size="14" font-weight="600">车险标的</text>
      </g>

      <!-- 大楼 -->
      <g class="building" transform="translate(680, 40)">
        <rect x="0" y="0" width="200" height="320" fill="url(#buildingBody)" stroke="#3d8fd9" stroke-width="2" />
        <g class="windows">
          <rect
            v-for="(win, i) in windows"
            :key="i"
            :x="win.x"
            :y="win.y"
            width="28"
            height="22"
            :fill="win.on ? '#ffe566' : 'rgba(0, 80, 140, 0.6)'"
            stroke="rgba(0, 200, 255, 0.3)"
          />
        </g>
        <text x="100" y="350" text-anchor="middle" fill="#a8d8ff" font-size="14" font-weight="600">承保机构</text>
      </g>
    </svg>
    <p class="center-visual__hint">车辆保障 ⇄ 机构承保 数据实时联动</p>
  </div>
</template>

<script setup lang="ts">
/** 大楼窗户亮灭(静态装饰) */
const windows = Array.from({ length: 24 }, (_, i) => {
  const col = i % 4
  const row = Math.floor(i / 4)
  return {
    x: 24 + col * 44,
    y: 30 + row * 58,
    on: (i + col) % 3 !== 0,
  }
})
</script>

<style scoped>
.center-visual {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 0;
}

.center-visual__svg {
  width: 100%;
  max-height: calc(100% - 32px);
  flex: 1;
}

.center-visual__hint {
  flex-shrink: 0;
  margin-top: 8px;
  font-size: 13px;
  color: rgba(140, 200, 255, 0.55);
  letter-spacing: 4px;
}

.beam {
  stroke-linecap: round;
}

.beam--1 {
  animation: beam-flicker 2.5s ease-in-out infinite;
}

.beam--2 {
  animation: beam-flicker 3s ease-in-out infinite 0.4s;
}

.beam--3 {
  stroke-dasharray: 8 12;
  animation: beam-dash 6s linear infinite;
}

.pulse {
  filter: drop-shadow(0 0 6px #00e5ff);
}

@keyframes beam-flicker {
  0%,
  100% {
    opacity: 0.75;
  }
  50% {
    opacity: 1;
  }
}

@keyframes beam-dash {
  to {
    stroke-dashoffset: -120;
  }
}
</style>

4、实时时钟

src/composables/useDashboardClock.ts

javascript 复制代码
import { onMounted, onUnmounted, ref } from 'vue'

const WEEKDAY_LABELS = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']

/** 数据大屏右上角实时时钟 */
export function useDashboardClock() {
  const timeText = ref('')
  const weekdayText = ref('')
  const dateText = ref('')

  function pad(n: number): string {
    return String(n).padStart(2, '0')
  }

  function tick(): void {
    const now = new Date()
    timeText.value = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
    weekdayText.value = WEEKDAY_LABELS[now.getDay()]
    dateText.value = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`
  }

  let timer: ReturnType<typeof setInterval> | undefined

  onMounted(() => {
    tick()
    timer = setInterval(tick, 1000)
  })

  onUnmounted(() => {
    if (timer) clearInterval(timer)
  })

  return { timeText, weekdayText, dateText }
}

5、路由 /data-screen

src/router/index.ts

javascript 复制代码
  {
    path: '/data-screen',
    name: 'DataScreen',
    component: () => import('@/views/dashboard/DataScreenView.vue'),
    meta: { title: '数据大屏' },
  },

6、首页增加入口卡片

src/views/home/HomeView.vue

javascript 复制代码
  {
    name: '数据大屏',
    desc: '车险智能运营可视化大屏',
    path: '/data-screen',
    icon: DataAnalysis,
    color: 'linear-gradient(135deg, #00d4ff, #0066cc)',
  },

成果图展示:

相关推荐
计算机魔术师39 分钟前
DeepSeek 据报道接近完成至少 800 亿元融资,腾讯与宁德时代参与
前端
可乐ea1 小时前
多模型编排的三层:框架、模型路由与提供商路由
前端·网络·人工智能·ai智能体·多智能体协作·多模型编排·大模型路由
却尘1 小时前
到底是选Workflow还是Agent?你真的搞明白你要什么了?别再盲目大力Agentic
ai编程
挖掘狂人1 小时前
从 "调模型" 到 "搭系统":Harness Engineering 凭什么成为 2026 年 AI 圈最热的新词
人工智能·agent·ai编程
Flynt2 小时前
这个日增 1400 星的 E2E 框架说"第二次跑不调模型",我改了按钮文案试了试
agent·ai编程·测试
飞哥数智坊2 小时前
AI 帮我把 Mac 下的 3D 鱼缸搬到了 Windows
人工智能·ai编程
青柠之夏cc3 小时前
前端拖拽功能原生实现,不引入拖拽库完成业务
开发语言·前端·python
liangshanbo12153 小时前
前端大文件切片上传怎么做?
前端
洛小豆4 小时前
我是 AI 牛马,老板说:做个简单的原生跨平台 Markdown 编辑器
人工智能·ai编程·markdown
Sand(ContextGate)4 小时前
Python Agent 测试实战:测试与评估,让 Agent 像传统软件一样可交付
前端·javascript·python·microsoft·ai