Skip to content

实现 UTM 链接生成器 工具

分类:developer | 标签:UTM、追踪、营销 生成带 utm 参数的追踪链接,支持批量渠道

2.1 功能说明

生成带 utm 参数的追踪链接,支持批量渠道

核心能力

  • 参数名
  • 原始值
  • 编码后

2.1.1 使用指南

功能说明

UTM 参数是投放链接上的一组标准标记,用来告诉统计工具「这个访客是从哪来的」。本工具帮你按规范拼接这些参数并正确做 URL 编码,避免手写时漏掉问号、写错连接符或让中文参数变成乱码。所有拼接都在浏览器本地完成,不会把链接上传到任何服务器。

五个参数怎么填

  • utm_source(必填):具体的流量来源站点或渠道,例如 wechat、baidu、newsletter。
  • utm_medium:媒介类型,描述这是哪一类流量,例如 cpc(付费点击)、social(社交)、email(邮件)、banner(横幅广告)。
  • utm_campaign:营销活动名称,用于把同一次活动的所有链接归到一起,例如 spring_sale_2026。
  • utm_term:付费搜索场景下的投放关键词。
  • utm_content:区分同一活动内的不同素材或位置,例如 top_banner 与 footer_link,便于做 A/B 对比。

拼接规则

  • 只有填写了内容的参数才会出现在链接里,留空的参数会被跳过。
  • 会自动识别链接中已有的查询参数,用 & 追加而不是重复加问号;链接末尾的锚点(井号后面的部分)会被保留并始终放在最后。
  • 参数值统一做 URL 编码,中文、空格和特殊符号都能安全传输。
  • 勾选「覆盖链接中已有的 utm 参数」后,原链接里旧的 utm 参数会先被移除再写入新值;不勾选则保留原有参数,可能出现同名参数重复。
  • 勾选「参数值统一转小写」有助于避免统计工具把 WeChat 和 wechat 当成两个来源。

批量生成

在渠道列表里每行填一个来源名,工具会用相同的 medium、campaign 等参数为每个渠道各生成一条链接,一行一条,可整体复制或下载。CSV 导出包含「渠道」与「链接」两列,带 UTF-8 BOM,可直接用 Excel 打开分发给各渠道负责人。

命名建议

建议团队内部统一使用小写字母加下划线的写法,并保持一份命名对照表。参数名本身(utm_source 等)是统计工具约定的固定写法,不能改动。

2.2 代码实现

2.2.1 组件结构

本工具是一个基于 Vue 3 <script setup> 语法的单文件组件(SFC),统一包裹在 ToolPageShell 组件内,由它提供页面标题、工具 ID、分类与「使用指南」插槽等通用外壳;核心业务逻辑(响应式状态、计算属性、事件处理函数)全部写在 <script setup> 中,输入/输出通过 el-inputel-button 等 Element Plus 组件与用户交互,所有数据均在浏览器本地处理,不会上传服务器。

2.2.2 核心逻辑概览

<script setup> 中定义的主要函数/方法:

  • normalizeValue()
  • buildUrl()
  • loadSample()
  • clearAll()
  • copyText()
  • saveBlob()
  • downloadText()
  • downloadBatchCsv()

2.2.3 关键实现代码

以下是该工具的完整 <script setup> 实现(核心代码)。模板(<template>)与样式(<style>)已省略。

vue
import { ref, reactive, computed } from 'vue'
import { ElMessage } from 'element-plus'
import ToolPageShell from '@/components/ToolPageShell.vue'

const baseUrl = ref('')
const overwriteExisting = ref(true)
const lowerCaseValues = ref(false)
const batchSources = ref('')

const params = reactive({
  source: '',
  medium: '',
  campaign: '',
  term: '',
  content: ''
})

const paramKeys = [
  ['source', 'utm_source'],
  ['medium', 'utm_medium'],
  ['campaign', 'utm_campaign'],
  ['term', 'utm_term'],
  ['content', 'utm_content']
]

function normalizeValue(v) {
  const trimmed = String(v || '').trim()
  return lowerCaseValues.value ? trimmed.toLowerCase() : trimmed
}

const paramRows = computed(() =>
  paramKeys
    .map(([field, key]) => {
      const raw = normalizeValue(params[field])
      return raw ? { key, raw, encoded: encodeURIComponent(raw) } : null
    })
    .filter(Boolean)
)

const urlWarning = computed(() => {
  const url = baseUrl.value.trim()
  if (!url) return ''
  if (!/^https?:\/\//i.test(url)) return '落地页地址建议以 http:// 或 https:// 开头,否则部分平台可能无法正确识别'
  if (/\s/.test(url)) return '落地页地址中包含空格,请检查是否粘贴出错'
  return ''
})

// 手工拆解链接,避免相对地址在 URL 构造函数中抛错
function buildUrl(sourceValue) {
  const url = baseUrl.value.trim()
  if (!url) return ''

  let rest = url
  let hash = ''
  const hashIndex = rest.indexOf('#')
  if (hashIndex !== -1) {
    hash = rest.slice(hashIndex)
    rest = rest.slice(0, hashIndex)
  }

  let path = rest
  let query = ''
  const qIndex = rest.indexOf('?')
  if (qIndex !== -1) {
    path = rest.slice(0, qIndex)
    query = rest.slice(qIndex + 1)
  }

  const utmNames = paramKeys.map(([, key]) => key)
  const existing = query
    .split('&')
    .filter(Boolean)
    .filter(item => {
      if (!overwriteExisting.value) return true
      const name = item.split('=')[0]
      return !utmNames.includes(name)
    })

  const added = []
  paramKeys.forEach(([field, key]) => {
    const raw = key === 'utm_source' && sourceValue !== undefined ? normalizeValue(sourceValue) : normalizeValue(params[field])
    if (raw) added.push(`${key}=${encodeURIComponent(raw)}`)
  })

  const all = [...existing, ...added]
  return path + (all.length ? '?' + all.join('&') : '') + hash
}

const singleUrl = computed(() => {
  if (!baseUrl.value.trim() || !normalizeValue(params.source)) return ''
  return buildUrl()
})

const batchList = computed(() =>
  batchSources.value
    .split(/\r?\n/)
    .map(s => s.trim())
    .filter(Boolean)
)

const batchResult = computed(() => {
  if (!baseUrl.value.trim() || !batchList.value.length) return ''
  return batchList.value.map(s => buildUrl(s)).join('\n')
})

function loadSample() {
  baseUrl.value = 'https://example.com/landing?ref=home'
  params.source = 'wechat'
  params.medium = 'social'
  params.campaign = 'spring_sale_2026'
  params.term = ''
  params.content = 'top_banner'
  batchSources.value = ['wechat', 'weibo', 'zhihu', 'douyin'].join('\n')
}

function clearAll() {
  baseUrl.value = ''
  paramKeys.forEach(([field]) => {
    params[field] = ''
  })
  batchSources.value = ''
}

async function copyText(text) {
  if (!text) return
  try {
    await navigator.clipboard.writeText(text)
    ElMessage.success('已复制到剪贴板')
  } catch {
    ElMessage.warning('复制失败,请手动选择文本复制')
  }
}

function saveBlob(content, filename, mime) {
  const blob = new Blob([content], { type: mime })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = filename
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
  ElMessage.success('已开始下载')
}

function downloadText(content, filename) {
  if (!content) return
  saveBlob(content, filename, 'text/plain;charset=utf-8')
}

function downloadBatchCsv() {
  if (!batchResult.value) return
  const escape = v => {
    const s = String(v)
    return /[",\n\r]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s
  }
  const lines = ['渠道,链接']
  batchList.value.forEach(s => lines.push([escape(s), escape(buildUrl(s))].join(',')))
  saveBlob('\ufeff' + lines.join('\r\n'), 'utm-links.csv', 'text/csv;charset=utf-8')
}

2.3 效果截图

UTM 链接生成器 效果截图

工具访问地址:https://www.i91tools.com/tools/utm-builder