标签 Vue3 下的文章

一、核心功能设计

时间戳转换器包含三个主要模块:

  1. 实时时间戳显示: 自动刷新的当前时间戳(秒/毫秒)
  2. 时间戳转日期: 将Unix时间戳转换为可读日期格式
  3. 日期转时间戳: 将日期时间转换为Unix时间戳

在线工具网址:https://see-tool.com/timestamp-converter

工具截图:
工具截图.png

二、实时时间戳显示实现

2.1 核心状态管理

// 响应式数据
const autoRefresh = ref(true)           // 自动刷新开关
const currentSeconds = ref(0)           // 当前秒级时间戳
const currentMilliseconds = ref(0)      // 当前毫秒级时间戳

let refreshInterval = null              // 定时器引用

2.2 更新时间戳逻辑

// 更新当前时间戳
const updateCurrentTimestamp = () => {
  if (!process.client) return           // SSR 保护
  const now = Date.now()                // 获取当前毫秒时间戳
  currentSeconds.value = Math.floor(now / 1000)  // 转换为秒
  currentMilliseconds.value = now
}

关键点:

  1. SSR 保护: 使用 process.client 判断,避免服务端渲染错误
  2. Date.now(): 返回毫秒级时间戳,性能优于 new Date().getTime()
  3. 秒级转换: 使用 Math.floor() 向下取整

2.3 自动刷新机制

// 监听自动刷新开关
watch(autoRefresh, (val) => {
  if (!process.client) return

  if (val) {
    updateCurrentTimestamp()            // 立即更新一次
    refreshInterval = setInterval(updateCurrentTimestamp, 1000)  // 每秒更新
  } else {
    if (refreshInterval) {
      clearInterval(refreshInterval)    // 清除定时器
      refreshInterval = null
    }
  }
})

关键点:

  1. 立即更新: 开启时先执行一次,避免1秒延迟
  2. 定时器管理: 关闭时清除定时器,防止内存泄漏
  3. 1秒间隔: setInterval(fn, 1000) 实现秒级刷新

2.4 生命周期管理

onMounted(() => {
  if (!process.client) return
  updateCurrentTimestamp()
  if (autoRefresh.value) {
    refreshInterval = setInterval(updateCurrentTimestamp, 1000)
  }
})

onUnmounted(() => {
  if (refreshInterval) {
    clearInterval(refreshInterval)      // 组件销毁时清理定时器
  }
})

说明:

  • 组件挂载时初始化时间戳和定时器
  • 组件卸载时必须清理定时器,防止内存泄漏

三、时间戳转日期实现

3.1 格式自动检测

// 检测时间戳格式(秒 or 毫秒)
const detectTimestampFormat = (ts) => {
  const str = String(ts)
  return str.length >= 13 ? 'milliseconds' : 'seconds'
}

判断依据:

  • 秒级时间戳: 10位数字 (如: 1706425716)
  • 毫秒级时间戳: 13位数字 (如: 1706425716000)
  • 临界点: 13位作为分界线

3.2 核心转换逻辑

const convertTimestampToDate = () => {
  if (!process.client) return
  if (!timestampInput.value.trim()) {
    safeMessage.warning(t('timestampConverter.notifications.enterTimestamp'))
    return
  }

  try {
    let ts = parseInt(timestampInput.value)

    // 自动检测或手动指定格式
    const format = tsInputFormat.value === 'auto'
      ? detectTimestampFormat(ts)
      : tsInputFormat.value

    // 统一转换为毫秒
    if (format === 'seconds') {
      ts = ts * 1000
    }

    const date = new Date(ts)

    // 验证日期有效性
    if (isNaN(date.getTime())) {
      safeMessage.error(t('timestampConverter.notifications.invalidTimestamp'))
      return
    }

    // ... 后续处理
  } catch (err) {
    safeMessage.error(t('timestampConverter.notifications.convertFailed'))
  }
}

关键点:

  1. 输入验证: 检查空值和有效性
  2. 格式统一: 统一转换为毫秒级时间戳
  3. 有效性检查: isNaN(date.getTime()) 判断日期是否有效
  4. 异常捕获: try-catch 保护,防止程序崩溃

3.3 时区处理

// 获取本地时区偏移
const getTimezoneOffset = () => {
  const offset = -date.getTimezoneOffset()  // 注意负号
  const hours = Math.floor(Math.abs(offset) / 60)
  const minutes = Math.abs(offset) % 60
  const sign = offset >= 0 ? '+' : '-'
  return `UTC${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`
}

说明:

  • getTimezoneOffset() 返回的是 UTC 与本地时间的分钟差
  • 返回值为正表示本地时间落后于 UTC,需要取反
  • 格式化为 UTC+08:00 形式
// 获取指定时区的偏移
const getTimezoneOffsetForZone = (timezone) => {
  if (timezone === 'local') {
    return getTimezoneOffset()
  }

  try {
    const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }))
    const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }))
    const offset = (tzDate - utcDate) / (1000 * 60)
    const hours = Math.floor(Math.abs(offset) / 60)
    const minutes = Math.abs(offset) % 60
    const sign = offset >= 0 ? '+' : '-'
    return `GMT${sign}${hours}`
  } catch (e) {
    return ''
  }
}

关键技巧:

  • 使用 toLocaleString()timeZone 参数转换时区
  • 通过 UTC 和目标时区的时间差计算偏移量
  • 异常捕获处理无效时区名称

3.4 日期格式化输出

// 根据选择的时区格式化本地时间
let localTime = date.toLocaleString(
  locale.value === 'en' ? 'en-US' : 'zh-CN',
  { hour12: false }
)

if (tsOutputTimezone.value !== 'local') {
  try {
    localTime = date.toLocaleString(
      locale.value === 'en' ? 'en-US' : 'zh-CN',
      {
        timeZone: tsOutputTimezone.value === 'UTC' ? 'UTC' : tsOutputTimezone.value,
        hour12: false
      }
    )
  } catch (e) {
    // 时区无效时回退到本地时间
    localTime = date.toLocaleString(
      locale.value === 'en' ? 'en-US' : 'zh-CN',
      { hour12: false }
    )
  }
}

格式化选项:

  • hour12: false: 使用24小时制
  • timeZone: 指定时区(如 'Asia/Shanghai', 'UTC')
  • 根据语言环境自动调整日期格式

3.5 年中第几天/第几周计算

// 计算年中第几天
const getDayOfYear = (d) => {
  const start = new Date(d.getFullYear(), 0, 0)  // 去年12月31日
  const diff = d - start
  const oneDay = 1000 * 60 * 60 * 24
  return Math.floor(diff / oneDay)
}

// 计算年中第几周
const getWeekOfYear = (d) => {
  const start = new Date(d.getFullYear(), 0, 1)  // 今年1月1日
  const days = Math.floor((d - start) / (24 * 60 * 60 * 1000))
  return Math.ceil((days + start.getDay() + 1) / 7)
}

算法说明:

  1. 年中第几天: 当前日期 - 去年最后一天 = 天数差
  2. 年中第几周: (天数差 + 1月1日星期几 + 1) / 7 向上取整

3.6 相对时间计算

// 相对时间(如: 3天前, 2小时后)
const getRelativeTime = (timestamp) => {
  if (!process.client) return ''

  const now = Date.now()
  const diff = now - timestamp
  const seconds = Math.abs(Math.floor(diff / 1000))
  const minutes = Math.floor(seconds / 60)
  const hours = Math.floor(minutes / 60)
  const days = Math.floor(hours / 24)

  const isAgo = diff > 0  // 是否是过去时间
  const units = tm('timestampConverter.timeUnits')

  let value, unit
  if (seconds < 60) {
    value = seconds
    unit = units.second
  } else if (minutes < 60) {
    value = minutes
    unit = units.minute
  } else if (hours < 24) {
    value = hours
    unit = units.hour
  } else {
    value = days
    unit = units.day
  }

  return isAgo
    ? t('timestampConverter.timeAgo', { value, unit })
    : t('timestampConverter.timeAfter', { value, unit })
}

逻辑分析:

  1. 时间差计算: 当前时间 - 目标时间
  2. 单位选择: 自动选择最合适的单位(秒/分/时/天)
  3. 方向判断: 正数为"前",负数为"后"
  4. 国际化: 使用 i18n 支持多语言

3.7 完整结果对象

const weekdays = tm('timestampConverter.weekdays')
const timezoneLabel = tsOutputTimezone.value === 'local'
  ? `${t('timestampConverter.localTimezone')} (${getTimezoneOffset()})`
  : `${tsOutputTimezone.value} (${getTimezoneOffsetForZone(tsOutputTimezone.value)})`

tsToDateResult.value = {
  timezone: timezoneLabel,           // 时区信息
  local: localTime,                  // 本地时间
  utc: date.toUTCString(),          // UTC 时间
  iso: date.toISOString(),          // ISO 8601 格式
  relative: getRelativeTime(ts),    // 相对时间
  dayOfWeek: weekdays[date.getDay()],  // 星期几
  dayOfYear: getDayOfYear(date),    // 年中第几天
  weekOfYear: getWeekOfYear(date)   // 年中第几周
}

四、日期转时间戳实现

4.1 设置当前时间

// 设置为当前时间
const setToNow = () => {
  if (!process.client) return
  const now = new Date()
  const year = now.getFullYear()
  const month = String(now.getMonth() + 1).padStart(2, '0')
  const day = String(now.getDate()).padStart(2, '0')
  const hours = String(now.getHours()).padStart(2, '0')
  const minutes = String(now.getMinutes()).padStart(2, '0')
  const seconds = String(now.getSeconds()).padStart(2, '0')
  dateTimeInput.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}

格式化技巧:

  • padStart(2, '0'): 补齐两位数(如: 9 → 09)
  • 月份需要 +1 (getMonth() 返回 0-11)
  • 格式: YYYY-MM-DD HH:mm:ss

4.2 核心转换逻辑

const convertDateToTimestamp = () => {
  if (!process.client) return

  if (!dateTimeInput.value) {
    safeMessage.warning(t('timestampConverter.notifications.selectDateTime'))
    return
  }

  try {
    const date = new Date(dateTimeInput.value)

    // 验证日期有效性
    if (isNaN(date.getTime())) {
      safeMessage.error(t('timestampConverter.notifications.invalidDateTime'))
      return
    }

    // 根据时区调整
    let finalDate = date

    if (dateInputTimezone.value === 'UTC') {
      // UTC 时区: 需要加上本地时区偏移
      finalDate = new Date(date.getTime() + date.getTimezoneOffset() * 60000)
    } else if (dateInputTimezone.value !== 'local') {
      // 其他时区: 计算时区差异
      const localDate = date
      const tzString = localDate.toLocaleString('en-US', {
        timeZone: dateInputTimezone.value
      })
      const tzDate = new Date(tzString)
      const offset = localDate.getTime() - tzDate.getTime()
      finalDate = new Date(localDate.getTime() - offset)
    }

    const ms = finalDate.getTime()
    const seconds = Math.floor(ms / 1000)

    dateToTsResult.value = {
      seconds,                    // 秒级时间戳
      milliseconds: ms,           // 毫秒级时间戳
      iso: finalDate.toISOString()  // ISO 8601 格式
    }

    safeMessage.success(t('timestampConverter.notifications.convertSuccess'))
  } catch (err) {
    safeMessage.error(t('timestampConverter.notifications.convertFailed'))
  }
}

时区处理详解:

  1. 本地时区 (local):

    • 直接使用用户输入的日期时间
    • 不做任何调整
  2. UTC 时区:

    • 用户输入的是 UTC 时间
    • 需要加上 getTimezoneOffset() 转换为本地时间戳
    • 例: 输入 "2024-01-01 00:00:00 UTC" → 北京时间 "2024-01-01 08:00:00"
  3. 其他时区 (如 Asia/Tokyo):

    • 计算目标时区与本地时区的偏移量
    • 通过 toLocaleString() 转换时区
    • 调整时间戳以反映正确的时间

4.3 时区转换原理

// 示例: 将 "2024-01-01 12:00:00" 从东京时区转换为时间戳

// 步骤1: 创建本地时间对象
const localDate = new Date('2024-01-01 12:00:00')  // 假设本地是北京时间

// 步骤2: 转换为东京时区的字符串
const tzString = localDate.toLocaleString('en-US', { timeZone: 'Asia/Tokyo' })
// 结果: "1/1/2024, 1:00:00 PM" (东京比北京快1小时)

// 步骤3: 将字符串解析为日期对象
const tzDate = new Date(tzString)

// 步骤4: 计算偏移量
const offset = localDate.getTime() - tzDate.getTime()
// offset = -3600000 (负1小时的毫秒数)

// 步骤5: 应用偏移量
const finalDate = new Date(localDate.getTime() - offset)

核心思想:

  • 通过两次转换计算时区差异
  • 利用偏移量调整时间戳
  • 确保时间戳代表的是正确的绝对时间

五、Date 对象核心 API 总结

6.1 创建日期对象

// 当前时间
new Date()                          // 当前日期时间
Date.now()                          // 当前时间戳(毫秒)

// 从时间戳创建
new Date(1706425716000)             // 毫秒时间戳
new Date(1706425716 * 1000)         // 秒时间戳需要 * 1000

// 从字符串创建
new Date('2024-01-28')              // ISO 格式
new Date('2024-01-28 12:00:00')     // 日期时间
new Date('Jan 28, 2024')            // 英文格式

// 从参数创建
new Date(2024, 0, 28)               // 年, 月(0-11), 日
new Date(2024, 0, 28, 12, 0, 0)     // 年, 月, 日, 时, 分, 秒

6.2 获取日期信息

const date = new Date()

// 获取年月日
date.getFullYear()      // 年份 (2024)
date.getMonth()         // 月份 (0-11, 0=1月)
date.getDate()          // 日期 (1-31)
date.getDay()           // 星期 (0-6, 0=周日)

// 获取时分秒
date.getHours()         // 小时 (0-23)
date.getMinutes()       // 分钟 (0-59)
date.getSeconds()       // 秒 (0-59)
date.getMilliseconds()  // 毫秒 (0-999)

// 获取时间戳
date.getTime()          // 毫秒时间戳
date.valueOf()          // 同 getTime()

// 时区相关
date.getTimezoneOffset()  // 本地时区与 UTC 的分钟差

6.3 设置日期信息

const date = new Date()

// 设置年月日
date.setFullYear(2024)
date.setMonth(0)        // 0-11
date.setDate(28)

// 设置时分秒
date.setHours(12)
date.setMinutes(30)
date.setSeconds(45)
date.setMilliseconds(500)

// 设置时间戳
date.setTime(1706425716000)

6.4 格式化输出

const date = new Date()

// 标准格式
date.toString()         // "Sun Jan 28 2024 12:00:00 GMT+0800 (中国标准时间)"
date.toDateString()     // "Sun Jan 28 2024"
date.toTimeString()     // "12:00:00 GMT+0800 (中国标准时间)"

// ISO 格式
date.toISOString()      // "2024-01-28T04:00:00.000Z"
date.toJSON()           // 同 toISOString()

// UTC 格式
date.toUTCString()      // "Sun, 28 Jan 2024 04:00:00 GMT"

// 本地化格式
date.toLocaleString()           // "2024/1/28 12:00:00"
date.toLocaleDateString()       // "2024/1/28"
date.toLocaleTimeString()       // "12:00:00"

// 自定义本地化
date.toLocaleString('zh-CN', {
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
  hour12: false,
  timeZone: 'Asia/Shanghai'
})

新闻文本分类识别系统

技术栈:前端Vue3+Element Plus,后端Flask,算法:TensorFlow+textCNN

项目介绍

本新闻文本分类识别系统是一个基于深度学习的智能文本分类Web应用平台。系统采用前后端分离架构,后端使用Python Flask框架提供RESTful API服务,前端采用Vue3框架结合Element Plus组件库构建现代化用户界面。核心算法基于TensorFlow深度学习框架,采用textCNN(卷积神经网络)模型对中文新闻文本进行自动分类,可识别体育、财经、房产、家居、教育、科技、时尚、时政、游戏、娱乐等十大类别。
图片
图片

选题背景与意义

随着互联网技术的飞速发展,网络新闻信息呈爆炸式增长,每天产生海量的新闻文本数据。传统的人工分类方式效率低下、成本高昂,已无法满足大数据时代的信息处理需求。自动文本分类技术作为自然语言处理的重要应用领域,能够快速准确地实现新闻内容的自动化归类,对于提高信息检索效率、实现个性化推荐、辅助内容监管具有重要意义。

关键技术栈:textCNN算法

textCNN(Text Convolutional Neural Network)是Yoon Kim于2014年提出的用于文本分类的卷积神经网络模型,其核心思想是利用一维卷积提取文本的局部特征。与传统CNN应用于图像处理不同,textCNN将词向量序列作为输入,通过不同尺寸的卷积核捕捉不同范围的语义特征(如词组、短语等)。

系统中的textCNN模型包含嵌入层、卷积层、池化层和全连接层。首先将文本转换为词向量矩阵表示,然后使用多个不同窗口大小的卷积核进行特征提取,通过最大池化操作保留最重要的特征信息,最后经Softmax激活函数输出各类别的概率分布。该模型在预训练的词向量基础上进行微调,相比RNN和LSTM等序列模型,textCNN具有训练速度快、参数量少、并行计算友好等优势。


系统架构图

图片

系统功能模块图

图片

演示视频 and 完整代码 and 安装

地址:https://www.yuque.com/ziwu/qkqzd2/bvlvc0up3rayte0t

终于到我水贴了

临近年前活少了, 最近在自己给自己找事做, 不然周报没法写.

这两天心血来潮,断断续续用 claudeCode 搭配 glm4.7 把公司快 7 年的许久没更新的官网给重构了, 使用下来就前端来说 glm 有 cc 九成功力不夸张.

前后大概 20 个页面,基本上 glm 都能很不错的完成迁移重构任务.

  • 老官网是 react16+js, 快 7 年没大改的老项目了, 我迁移到了 vue3+vite7+ts
  • 我最常说的话是"继续实现剩下的页面"
  • glm 最常犯的错误是各种"margin/padding 边距问题", 以及 css 的未生效问题(被直接迁移过来的全局样式覆盖)
  • 全程只碰到了一个轮播图的复刻,是 glm 死活没法复刻的, 因为原代码写的稀碎. 我跟 glm 纠缠了大概 1 个小时它就是改不对, 不是偏移就是丢失动画. 之后我切换到了转发 api 的 claudeCode api 满共说了三句修改好了~~~, 但是费用也感人花了 5 块钱

这个应该算是我第一个商业的纯 ai 练手项目, 综合体验下来 glm 的确是可以作为备用甚至主力辅助方案的.

便宜效果不错, 因为是国产还能直接在公司及部门内部推广.

花费

glm 这边我花了 900 万的 token, 按纯 api 套餐 1000 万 19.9 的花费来说, 也就是差不多 20 块钱.

我是 200 包年的 lite 套餐, 所以算下来会更便宜, 很划算.

时间方面, 第一天下班前我用满了 5 小时消耗, 下一次的刷新时间还有 40 分钟, 就没有在继续干活了.

所以如果是同一时间一个人单一项目使用的话, lite 套餐是绝对划算够用的.

转发 api 花费了 20 万 token, 消耗是 26 块钱. cc 转发 api 的确是贵啊. 修一个轮播图就 5 块钱, 但是效果的确拔群 glm1 小时没折腾好的,切换 cc 不到 10 分钟搞定.

花絮

在重构过程中, 还顺手让 glm 帮忙处理了下业务妹子的问题, 把一个 700mb 的 excel 里的 E 列的图片导出来并用 B 列的名称重命名. 这种临时一次性的代码, 往次都是边跟妹妹聊天边写脚本做的, 这次也是一句话 glm10 分钟搞定了, 没有二次对话一遍过.

效果的确是好的多, 业务妹子还问我 wps 的 ai 有没有这效果... 我说 wps 的 ai 就不用考虑了...

在线工具网址:https://see-tool.com/date-calculator

工具截图:

一、核心功能设计

日期计算器包含四个独立模块:

  1. 日期间隔计算: 计算两个日期之间的天数、周数、月数、年数
  2. 日期加减计算: 在基准日期上加减指定时间单位
  3. 年龄计算: 精确计算年龄(年/月/日)
  4. 工作日计算: 统计工作日、周末天数

二、日期间隔计算实现

2.1 核心计算逻辑

const dateDiff = computed(() => {
  if (!startDate.value || !endDate.value) {
    return { days: 0, weeks: 0, months: 0, years: 0 }
  }

  const start = new Date(startDate.value)
  const end = new Date(endDate.value)

  // 确保开始日期小于结束日期(自动排序)
  const [earlierDate, laterDate] = start <= end ? [start, end] : [end, start]

  let diffTime = laterDate.getTime() - earlierDate.getTime()

  // 如果包含结束日期,增加一天
  if (includeEndDate.value) {
    diffTime += 24 * 60 * 60 * 1000
  }

  const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))

  // 计算精确的月数差异
  let months = (laterDate.getFullYear() - earlierDate.getFullYear()) * 12
  months += laterDate.getMonth() - earlierDate.getMonth()

  // 如果日期不足一个月,减去一个月
  if (laterDate.getDate() < earlierDate.getDate()) {
    months--
  }

  // 计算年数
  const years = Math.floor(months / 12)

  return {
    days: diffDays,
    weeks: Math.floor(diffDays / 7),
    months: Math.max(0, months),
    years: Math.max(0, years)
  }
})

关键点:

  1. 自动排序: 无论用户输入顺序,自动识别较早和较晚的日期
  2. 包含结束日期: 可选项,影响天数计算(+1天)
  3. 精确月数: 考虑日期不足一个月的情况
  4. 负数保护: 使用 Math.max(0, value) 防止负数

2.2 辅助工具函数

// 交换开始和结束日期
const swapDates = () => {
  const temp = startDate.value
  startDate.value = endDate.value
  endDate.value = temp
}

// 设置结束日期为今天
const setToday = (type) => {
  if (!process.client) return
  const today = new Date().toISOString().split('T')[0]
  if (type === 'diff') {
    endDate.value = today
  }
}

三、日期加减计算实现

3.1 核心计算逻辑

const calculatedDate = computed(() => {
  if (!baseDate.value) {
    return ''
  }

  if (!amount.value || amount.value === 0) {
    return baseDate.value
  }

  const base = new Date(baseDate.value)
  // 根据操作类型确定正负
  const value = operation.value === 'add' ? parseInt(amount.value) : -parseInt(amount.value)

  switch (unit.value) {
    case 'days':
      base.setDate(base.getDate() + value)
      break
    case 'weeks':
      base.setDate(base.getDate() + (value * 7))
      break
    case 'months':
      base.setMonth(base.getMonth() + value)
      break
    case 'years':
      base.setFullYear(base.getFullYear() + value)
      break
  }

  return base.toISOString().split('T')[0]
})

关键点:

  1. 操作符处理: 减法通过负数实现,统一使用加法逻辑
  2. 原生 Date API: 利用 setDate/setMonth/setFullYear 自动处理溢出
  3. 格式化输出: toISOString().split('T')[0] 获取 YYYY-MM-DD 格式

3.2 获取星期几

const getWeekday = (dateStr) => {
  if (!dateStr) return ''
  const weekdays = tm('dateCalculator.weekdays')
  if (!weekdays || !Array.isArray(weekdays)) return ''
  const date = new Date(dateStr)
  return weekdays[date.getDay()] || ''
}

说明:

  • getDay() 返回 0-6,其中 0 代表周日
  • 从国际化配置中读取星期名称数组

四、年龄计算实现

4.1 精确年龄计算

const age = computed(() => {
  if (!birthDate.value || !ageCalculateDate.value) {
    return { years: 0, months: 0, days: 0, totalDays: 0 }
  }

  const birth = new Date(birthDate.value)
  const calculate = new Date(ageCalculateDate.value)

  // 如果出生日期晚于计算日期,返回0
  if (birth > calculate) {
    return { years: 0, months: 0, days: 0, totalDays: 0 }
  }

  // 计算精确年龄
  let years = calculate.getFullYear() - birth.getFullYear()
  let months = calculate.getMonth() - birth.getMonth()
  let days = calculate.getDate() - birth.getDate()

  // 调整天数
  if (days < 0) {
    months--
    // 获取上个月的天数
    const lastMonth = new Date(calculate.getFullYear(), calculate.getMonth(), 0)
    days += lastMonth.getDate()
  }

  // 调整月数
  if (months < 0) {
    years--
    months += 12
  }

  // 计算总天数
  const totalDays = Math.floor((calculate.getTime() - birth.getTime()) / (1000 * 60 * 60 * 24))

  return {
    years: Math.max(0, years),
    months: Math.max(0, months),
    days: Math.max(0, days),
    totalDays: Math.max(0, totalDays)
  }
})

关键点:

  1. 逐级调整: 先调整天数,再调整月数,最后得到年数
  2. 借位逻辑: 天数不足时从月份借位,月份不足时从年份借位
  3. 上月天数: 使用 new Date(year, month, 0) 获取上月最后一天
  4. 总天数: 单独计算,用于显示"已活xx天"

4.2 派生数据计算

// 模板中使用
Math.floor(age.totalDays / 30.44)  // 总月数(平均每月30.44天)
Math.floor(age.totalDays / 7)      // 总周数
age.totalDays                      // 总天数

五、工作日计算实现

5.1 核心计算逻辑

const workDays = computed(() => {
  if (!workStartDate.value || !workEndDate.value) {
    return { total: 0, weekdays: 0, weekends: 0 }
  }

  const start = new Date(workStartDate.value)
  const end = new Date(workEndDate.value)

  // 确保开始日期不大于结束日期
  if (start > end) {
    return { total: 0, weekdays: 0, weekends: 0 }
  }

  let weekdays = 0
  let weekends = 0
  const current = new Date(start)

  // 包含开始和结束日期
  while (current <= end) {
    const dayOfWeek = current.getDay()
    if (dayOfWeek === 0 || dayOfWeek === 6) { // 周日=0, 周六=6
      weekends++
    } else {
      weekdays++
    }
    current.setDate(current.getDate() + 1)
  }

  return {
    total: weekdays + weekends,
    weekdays: excludeWeekends.value ? weekdays : weekdays + weekends,
    weekends
  }
})

关键点:

  1. 逐日遍历: 从开始日期循环到结束日期,逐日判断
  2. 星期判断: getDay() 返回 0(周日) 或 6(周六) 为周末
  3. 可选排除: 根据 excludeWeekends 决定是否排除周末
  4. 包含边界: 包含开始和结束日期

六、状态管理

6.1 响应式状态定义

// Tab 切换
const activeTab = ref('difference')

// 日期间隔计算
const startDate = ref('')
const endDate = ref('')
const includeEndDate = ref(false)

// 日期加减计算
const baseDate = ref('')
const operation = ref('add')      // 'add' | 'subtract'
const amount = ref(0)
const unit = ref('days')          // 'days' | 'weeks' | 'months' | 'years'

// 工作日计算
const workStartDate = ref('')
const workEndDate = ref('')
const excludeWeekends = ref(true)

// 年龄计算
const birthDate = ref('')
const ageCalculateDate = ref('')

6.2 初始化默认值

onMounted(() => {
  if (!process.client) return
  const today = new Date().toISOString().split('T')[0]
  startDate.value = today
  endDate.value = today
  baseDate.value = today
  workStartDate.value = today
  workEndDate.value = today
  birthDate.value = ''  // 不设置默认出生日期
  ageCalculateDate.value = today
})

说明:

  • 使用 process.client 判断避免 SSR 问题
  • 出生日期不设默认值,避免误导用户

七、日期处理技巧

7.1 Date 对象的自动溢出处理

// JavaScript 的 Date 会自动处理溢出
const date = new Date('2024-01-31')
date.setMonth(date.getMonth() + 1)  // 自动变为 2024-03-02(2月没有31日)

7.2 获取上月最后一天

// 将日期设为0,会自动回退到上月最后一天
const lastDayOfLastMonth = new Date(year, month, 0)

7.3 日期格式化

// ISO 格式转 YYYY-MM-DD
const dateStr = new Date().toISOString().split('T')[0]

八、核心算法总结

日期间隔计算:
  时间戳相减 → 转换为天数
  年月日逐级计算 → 处理借位

日期加减计算:
  原生 Date API → 自动处理溢出

年龄计算:
  年月日分别相减 → 逐级调整借位

工作日计算:
  逐日遍历 → 判断星期几 → 统计分类

核心原则:

  1. 利用原生 API: Date 对象的自动溢出处理
  2. 边界处理: 防止负数、空值、非法日期
  3. 精确计算: 考虑月份天数差异、闰年等特殊情况
  4. 用户友好: 自动排序、可选配置、实时计算

Vue3 核心知识点读书笔记

一、Vue 核心原理与架构

1. MVVM 核心模式(核心架构)

Vue 基于 MVVM 模式设计,核心是实现视图与数据的解耦,三者关系如下:

模块核心职责
Model数据层,负责业务数据处理(纯数据,无视图交互逻辑)
View视图层,即用户界面(仅展示内容,不处理数据逻辑)
ViewModel桥梁层,连接 View 和 Model,包含两个核心能力:
✅ DOM Listeners:监听 View 中 DOM 变化,同步到 Model
✅ Data Bindings:监听 Model 中数据变化,同步到 View
关键:View 和 Model 不能直接通信,必须通过 ViewModel 中转,实现解耦。

2. Vue 核心特性(四大核心)

特性具体说明示例/应用场景
数据驱动视图数据变化自动触发视图重新渲染,无需手动操作 DOM修改变量值 → 页面自动更新
双向数据绑定视图变化 ↔ 数据变化双向同步表单输入框内容自动同步到数据变量
指令分内置指令(Vue 自带)和自定义指令,以v-开头绑定到 DOM 元素v-bind(单向绑定)、v-if(条件渲染)、v-for(列表渲染)
插件支持扩展功能,配置简单VueRouter(路由)、Pinia(状态管理)

二、Vue 版本与开发环境

1. Vue2 vs Vue3 核心差异

维度Vue3 变化
新增功能组合式(Composition)API、多根节点组件、底层渲染/响应式逻辑重构(性能提升)
废弃功能过滤器(Filter)、$on()/$off()/$once() 实例方法
兼容性兼容 Vue2 绝大多数 API,新项目推荐直接使用 Vue3

2. 开发环境准备(必装)

  1. 编辑器:VSCode → 安装「Vue (Official)」扩展(提供代码高亮、语法提示)
  2. 运行环境:Node.js(官网下载安装,为包管理工具提供基础)
  3. 包管理工具:npm/yarn(管理第三方依赖,支持一键安装/升级/卸载,避免手动下载解压)

三、Vite 创建 Vue3 项目(核心操作)

1. 项目创建命令(适配 npm10 版本)

# Yarn 方式(推荐)
yarn create vite hello-vite --template vue

# 交互提示处理(关键步骤,不要遗漏):
# 1. 提示 "Use rolldown-vite (Experimental)?" → 回车选 No(优先使用稳定版)
# 2. 提示 "Install with yarn and start now?" → 回车选 Yes(自动安装依赖并启动项目)

2. 手动创建命令(补充)

# npm 方式
npm create vite@latest
# yarn 方式
yarn create vite
# 后续需手动填写项目名称、选择框架(Vue)、选择变体(JavaScript)

四、Vue3 项目核心文件与目录

1. 项目目录结构(重点关注)

hello-vite/          # 项目根目录
├── node_modules/    # 第三方依赖包(自动生成)
├── dist/            # 构建产物(执行 yarn build 后生成,用于部署)
├── src/             # 源代码目录(开发核心)
│   ├── assets/      # 静态资源(图片、样式等)
│   ├── components/  # 自定义组件
│   ├── App.vue      # 根组件
│   ├── main.js      # 项目入口文件
│   └── style.css    # 全局样式
├── index.html       # 页面入口文件
└── package.json     # 项目配置(依赖、脚本命令)

2. 核心文件代码解析(带完整注释)

(1)index.html(页面入口)
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>hello-vite</title>
  </head>
  <body>
    <!-- Vue 实例挂载容器:被 main.js 中的 Vue 实例控制 -->
    <div id="app"></div>
    <!-- type="module":启用 ES6 模块化语法,引入项目入口文件 -->
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
(2)src/main.js(项目入口,创建 Vue 实例)
// 从 Vue 中导入创建应用实例的核心函数
import { createApp } from 'vue'
// 导入全局样式文件
import './style.css'
// 导入根组件(App.vue)
import App from './App.vue'

// 方式1:简洁写法(创建实例 + 挂载到 #app 容器)
createApp(App).mount('#app')

// 方式2:分步写法(更易理解,效果一致)
// const app = createApp(App) // 创建 Vue 应用实例
// app.mount('#app') // 挂载实例(仅可调用一次)
(3)src/App.vue(根组件,单文件组件核心)
<!-- script setup:Vue3 组合式 API 语法糖,简化组件编写 -->
<script setup>
// 导入子组件(HelloWorld.vue)
import HelloWorld from './components/HelloWorld.vue'
</script>

<!-- template:组件模板结构(视图部分) -->
<template>
  <div>
    <a href="https://vite.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <!-- 使用子组件,传递 msg 属性 -->
  <HelloWorld msg="Vite + Vue" />
</template>

<!-- style scoped:样式仅作用于当前组件(通过 Hash 隔离,不影响子组件) -->
<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

五、核心知识点总结

1. 核心原理

  • Vue 基于 MVVM 模式,通过 ViewModel 实现视图与数据的双向驱动,核心是「数据驱动视图」,无需手动操作 DOM;
  • 双向数据绑定是 Vue 核心特性,表单场景下可自动同步视图与数据。

2. 项目开发

  • Vue3 推荐使用 Vite 创建项目(比 VueCLI 更快),npm10 版本下优先用 yarn create vite 项目名 --template vue 命令;
  • 项目核心文件:index.html(页面入口)→ main.js(创建 Vue 实例)→ App.vue(根组件),三者构成项目基础骨架。

3. 关键注意点

  • mount() 方法仅可调用一次,挂载目标可以是 DOM 元素或 CSS 选择器(#app/.app);
  • <style scoped> 样式仅作用于当前组件,避免样式污染;
  • Vue3 废弃了过滤器、$on/$off/$once 等功能,开发时需避开。

图片云上传 IMG-CLOUD-UPDATE

项目介绍

在写博客的场景下,不可避免上传一些图片。作为博客文章插图。

我都是直接上传到我阿里云的 oss 里面。但是阿里云自带的工具太重了,每次打开等半天,而且复制 url 还得点好几下。

我花了一周做了一个快速上传图片到阿里云 OSS ,并且直接生成 makedown 格式的图片链接,方便你写博客或文章的时候快速上传并插入。

下面是正经的介绍:


图片云上传是一款前端使用 vue3+element-plus ,后端使用 go+gin+bbolt 的图片上传项目。使用该项目可以让你更快的把本地图片传到远程图片云仓库。当前版本支持阿里云 oss 。不用担心你的 access_key_id 会泄露,本系统保存到本地二进制数据库,不做任何远程传输。

本项目也适合初学者学习参考,内有大量的注释,对于学习 golang 和 VUE 都有比较大的帮助。

已支持:

  • 阿里云 oss
  • 自定义路径
  • 自定义/自动修改名称
  • 自动添加 markdown 格式
  • 支持设置密码/默认安装页面

未支持

  • 显示多层文件列表
  • aws/腾讯云/七牛等更多远程仓库

截图


https://imgur.com/1kA2Bav

https://imgur.com/gIbloDS

依赖组件:

1. 前端:

  1. vue3
  2. element-plus
  3. pinia
  4. axios
  5. vue-router

2. 后端:

  1. go
  2. gin
  3. bbolt

安装

  1. docker 编译安装

直接执行 make 即可,会先编译 web 端,再编译 server 端。再打包容器镜像。最后 docker 运行镜像。

参考项目:

这是一个三维地图应用,可以在上面编写动作操作场景变化,我看到 B 站上的 up 发关于世界局势的时候,经常用这种地球视角来讲解,就想着自己做一个看看效果。主要技术栈为 Vue3 + Vite + TS + Cesum 。

特点:

  • 动作编辑。现在就只有两个动作,飞都某地以及环绕某一个地点飞行。
  • 链接分享。可以生成包含动作都链接。
  • 链接导入,导出。

当前还没开源呢,我计划给它增加几个动作,例如切换地图,添加自定义图像,显示文字,播放声音等。

我觉得现在这个 UI 好丑,但是我没有美术功底,期待 V 友反馈哈哈哈。

demo 地址: https://giserlab.cn/app/mapflow/

下面这个很长的是分享链接,进去会有默认动作。

https://giserlab.cn/app/mapflow/?actions=NobwRA1gpgnmBcYCGBGAxgBgGwA4AsArALRICceaReARlhkdQMwAmxSaaOAZgYwOxoATNQxgANGAB2SALZQEYQJHagSBVA-criwzKAGc0AJwCWABwAuBgPaSFGkzCPzEXADZwJXA5IPaAFlGYIuJCdtKAlmJBMkBHAnSwQUFFIAOlwMQSwUCScIhEYMFMYcHAxGCSCTXJK6MIBXPQiLK3gCAF8WsXBoOERGNAJmPKw+IgJ+riouQVIiUhRmPCIhUkFAjDxewRwNaTkFQHRlQEW5QC5lDS1dQ1NG6wlbewVnVzB3Tx8-AKCQsIio+Bi4+BQghQSXweDomTA2Qq8CmINIpAIeBwZSc0JQvFIGFq9TM-z4bQ6kFgCkxeC4gUEjCIGGY1EoeAoxHI8yIuDJaCwWGEaCg1G2sgcYEAF3aAfRsTmEdPpjLimmAbHZBQ8NM8vL5-PBAsFQppvtFIf9AfkgYkcBCoZUkoIMDhGIxBCjoQQMM6sZo6g1-lgCZ1iYgihg+KMkOMMARMFQ+FAmVw6VREaNbRgkHwEvzdohReLNJKLjKFIBVv0AexmZ+V3RBIPTmGqSfxuDyqt4aj7a8KRPWxJqGy0JUimrI5eB5S3W2325CohBW12+AwAc28aJdrvqklnDidzokRgMJjQ3gQcexHs7gm9RO6YDwKCwtIySCopCwCzwtPopGYHKI+GoKD4jBQJSUqQaaCoAs0GAOrK6gSuc0pXIgpaKi4yr1q86qap8Optr8+onvkeCCAQCJ8P20KCHwSQECgOBYIwWBHnm8CkA6CCcgBZ5dAoUBIJyUB8GSRBQLR9KRlADA8oIRBaOQnJWowUBYAQIEKIAHHqAI46UHZjBlz-HKNwKvcSF1i8arvFqXxYX8nZURRCSCIIeAkZOOBJJi9lgvRcGAsx8D4Rg7G+k8BBIOwf4LHwVRUPwlA4KQUAoNSgjMEC1B8FwWBINQikSDsgqALhKgDTmoAY5GAH9qpw5rBOkIQZjwqqhpkYa2PyWfEVpJCayyOYORoEHwRQeZ63kEXQS5tAAukAA

各位 L 站的佬友们好!

我是本坛萌新,潜水有一段时间了,一直在这个技术氛围浓厚的社区里学习。今天终于鼓起勇气发个贴,分享一个自己最近开发的练手工具,希望各位佬轻喷,也欢迎大家多提宝贵意见!

开发初衷

平时用夸克网盘比较多,但官方客户端的广告和臃肿大家都懂。加上我有自动化整理资源的需求,官方缺少 API 支持。
作为一名开发者,手痒之下就用 Go (Wails) + Vue3 + Element Plus 撸了这个第三方客户端。

目前项目已经打包了 Windows 版本(单文件绿色版),主打一个干净、无广、可编程,发出来分享给有需要的坛友们体验。

[软件分享] QuarkManager - 萌新首作,基于 Wails + Vue3 打造的夸克网盘桌面端1 [软件分享] QuarkManager - 萌新首作,基于 Wails + Vue3 打造的夸克网盘桌面端2 [软件分享] QuarkManager - 萌新首作,基于 Wails + Vue3 打造的夸克网盘桌面端3 [软件分享] QuarkManager - 萌新首作,基于 Wails + Vue3 打造的夸克网盘桌面端4

界面预览





核心亮点

除了基础的文件管理(上传 / 下载 / 重命名 / 移动等),针对像我这样的 “折腾党”,做了以下增强:

  • 多账户无缝切换:支持同时登录多个账号,一键切换,适合多号党。
  • 内置 API 服务:自带 Swagger 文档的 HTTP API(默认 8080 端口),支持外部程序调用,方便自己写脚本对接网盘。
  • Cookie 过期提醒:支持配置 SMTP 邮件通知,Cookie 快过期时自动发邮件提醒,避免自动任务挂掉。
  • 强大的分享与转存:支持批量转存带密码的链接,自动解析,支持设置分享有效期。

技术栈分享

虽然目前代码还在整理中暂未开源,但还是想和大家交流一下技术选型。
Wails 的方案在 Windows 下表现真的非常不错,利用系统自带的 WebView2,体积比 Electron 小很多,内存占用也低,Go 写后端逻辑也很舒服。

  • Backend: Go (Wails 框架)
  • Frontend: Vue 3 + Element Plus
  • API: 内置 HTTP Server + Swagger

进阶玩法:API 调用

这是我个人最喜欢的功能,开启 API 服务后,你可以直接用 curl 或者 python 操作网盘,做一些自动化的事情:

# 1. 获取文件列表
curl "http://localhost:8080/api/files?folder_id=0" # 2. 一键转存分享链接
curl -X POST http://localhost:8080/api/save \
  -H "Content-Type: application/json" \
  -d '{"share_url": "[https://pan.quark.cn/s/xxx](https://pan.quark.cn/s/xxx)"}'

访问 http://localhost:8080/swagger/ 还能看到完整的接口文档。

📥 下载与安装
目前 Release 页面已上传编译好的 quarkpan.exe,下载解压即可直接运行。

GitHub 发布页: https://github.com/dpyyds/QuarkManager/releases

⚠️ 免责声明
本软件为第三方个人开发工具,仅供学习交流,严禁用于商业用途。

使用第三方客户端可能存在风险,请大家自行评估,后果自负。

如涉及侵权请联系删除。

初来乍到,希望能融入 L 站这个大家庭。如果觉得这个小工具好用,求各位佬去 GitHub 点个 Star 🌟 支持一下,也欢迎在评论区交流 bug 和建议!

📌 转载信息
转载时间:
2026/1/16 12:59:10

各位 L 站的佬友们好!

我是本坛萌新,潜水有一段时间了,一直在这个技术氛围浓厚的社区里学习。今天终于鼓起勇气发个贴,分享一个自己最近开发的练手工具,希望各位佬轻喷,也欢迎大家多提宝贵意见!

开发初衷

平时用夸克网盘比较多,但官方客户端的广告和臃肿大家都懂。加上我有自动化整理资源的需求,官方缺少 API 支持。
作为一名开发者,手痒之下就用 Go (Wails) + Vue3 + Element Plus 撸了这个第三方客户端。

目前项目已经打包了 Windows 版本(单文件绿色版),主打一个干净、无广、可编程,发出来分享给有需要的坛友们体验。

[软件分享] QuarkManager - 萌新首作,基于 Wails + Vue3 打造的夸克网盘桌面端1 [软件分享] QuarkManager - 萌新首作,基于 Wails + Vue3 打造的夸克网盘桌面端2 [软件分享] QuarkManager - 萌新首作,基于 Wails + Vue3 打造的夸克网盘桌面端3 [软件分享] QuarkManager - 萌新首作,基于 Wails + Vue3 打造的夸克网盘桌面端4

界面预览





核心亮点

除了基础的文件管理(上传 / 下载 / 重命名 / 移动等),针对像我这样的 “折腾党”,做了以下增强:

  • 多账户无缝切换:支持同时登录多个账号,一键切换,适合多号党。
  • 内置 API 服务:自带 Swagger 文档的 HTTP API(默认 8080 端口),支持外部程序调用,方便自己写脚本对接网盘。
  • Cookie 过期提醒:支持配置 SMTP 邮件通知,Cookie 快过期时自动发邮件提醒,避免自动任务挂掉。
  • 强大的分享与转存:支持批量转存带密码的链接,自动解析,支持设置分享有效期。

技术栈分享

虽然目前代码还在整理中暂未开源,但还是想和大家交流一下技术选型。
Wails 的方案在 Windows 下表现真的非常不错,利用系统自带的 WebView2,体积比 Electron 小很多,内存占用也低,Go 写后端逻辑也很舒服。

  • Backend: Go (Wails 框架)
  • Frontend: Vue 3 + Element Plus
  • API: 内置 HTTP Server + Swagger

进阶玩法:API 调用

这是我个人最喜欢的功能,开启 API 服务后,你可以直接用 curl 或者 python 操作网盘,做一些自动化的事情:

# 1. 获取文件列表
curl "http://localhost:8080/api/files?folder_id=0" # 2. 一键转存分享链接
curl -X POST http://localhost:8080/api/save \
  -H "Content-Type: application/json" \
  -d '{"share_url": "[https://pan.quark.cn/s/xxx](https://pan.quark.cn/s/xxx)"}'

访问 http://localhost:8080/swagger/ 还能看到完整的接口文档。

📥 下载与安装
目前 Release 页面已上传编译好的 quarkpan.exe,下载解压即可直接运行。

GitHub 发布页: https://github.com/dpyyds/QuarkManager/releases

⚠️ 免责声明
本软件为第三方个人开发工具,仅供学习交流,严禁用于商业用途。

使用第三方客户端可能存在风险,请大家自行评估,后果自负。

如涉及侵权请联系删除。

初来乍到,希望能融入 L 站这个大家庭。如果觉得这个小工具好用,求各位佬去 GitHub 点个 Star 🌟 支持一下,也欢迎在评论区交流 bug 和建议!

📌 转载信息
转载时间:
2026/1/15 18:15:48