<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title><![CDATA[树下薰衣草]]></title> 
<description><![CDATA[树下薰衣草个人博客，分享生活]]></description>
<link>https://suxiaojun.top/</link>
<language>zh-cn</language>
<generator>www.emlog.net</generator>
<item>
    <title>封装时间格式组件</title>
    <link>https://suxiaojun.top/?post=21</link>
    <description><![CDATA[<pre><code class="language-javascript">// utils/formatDate.ts

/**
 * 按指定格式格式化时间戳
 * @param timestamp 时间戳（number | string），可为毫秒数或可被 new Date() 解析的字符串
 * @param format 输出格式，默认 YYYY-MM-DD hh:mm:ss
 *  - YYYY: 4 位年份
 *  - YY: 2 位年份
 *  - MM/M: 月（两位/一位）
 *  - DD/D: 日（两位/一位）
 *  - HH/H: 24 小时制（两位/一位）
 *  - hh/h: 12 小时制（两位/一位）
 *  - mm/m: 分钟（两位/一位）
 *  - ss/s: 秒（两位/一位）
 *  - A: AM / PM
 */
export function formatDate(timestamp: number | string, format = 'YYYY-MM-DD hh:mm:ss'): string {
  // 若传入空值，返回空字符串
  if (!timestamp) return ''

  let ts: any = null

  // 如果 timestamp 不是数字，则直接作为日期字符串处理
  // 否则转为数字（时间戳）
  if (isNaN(Number(timestamp))) {
    ts = timestamp
  } else {
    ts = Number(timestamp)
  }

  const date = new Date(ts)

  // new Date 无法解析时，返回 Invalid Date
  if (isNaN(date.getTime())) return 'Invalid Date'

  // 基本日期结构
  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()
  const hours24 = date.getHours()
  const minutes = date.getMinutes()
  const seconds = date.getSeconds()

  // 处理 12 小时制 AM/PM
  const ampm = hours24 &gt;= 12 ? 'PM' : 'AM'
  const hours12 = hours24 % 12 || 12 // 0 点转换成 12 点

  // 格式中是否出现 A（表示使用 12 小时制）
  const use12Hour = format.includes('A')

  /**
   * 占位符映射表
   * 不同的 key 会对应不同的日期组件内容
   */
  const map: Record&lt;string, string&gt; = {
    YYYY: String(year),
    YY: String(year).slice(-2),
    MM: String(month).padStart(2, '0'),
    M: String(month),
    DD: String(day).padStart(2, '0'),
    D: String(day),
    hh: String(hours12).padStart(2, '0'),
    h: String(hours12),
    HH: String(hours24).padStart(2, '0'),
    H: String(hours24),
    mm: String(minutes).padStart(2, '0'),
    m: String(minutes),
    ss: String(seconds).padStart(2, '0'),
    s: String(seconds),
    A: ampm, // AM/PM
  }

  // 如果格式包含 A，则 hh/h 应使用 12 小时制
  if (use12Hour) {
    map.hh = String(hours12).padStart(2, '0')
    map.h = String(hours12)
  } else {
    // 未使用 A，则 hh/h 默认等同于 24 小时制（HH/H）
    map.hh = map.HH
    map.h = map.H
  }

  // 遍历所有占位符并进行替换
  let formatted = format
  for (const key in map) {
    formatted = formatted.replace(new RegExp(key, 'g'), map[key])
  }

  return formatted
}
</code></pre>]]></description>
    <pubDate>Tue, 02 Dec 2025 17:37:07 +0800</pubDate>
    <author>suxiaojun</author>
    <guid>https://suxiaojun.top/?post=21</guid>
</item>
<item>
    <title>防抖节流函数封装成独立组件</title>
    <link>https://suxiaojun.top/?post=20</link>
    <description><![CDATA[<p>防抖函数</p>
<pre><code class="language-javascript">/**
 * @description 防抖函数
 * @param fn 需要防抖的方法
 * @param delay 延迟时间
 * @returns
 */
export function debounce&lt;T extends (...args: any[]) =&gt; void&gt;(
  fn: T,
  delay = 300
) {
  let timer: ReturnType&lt;typeof setTimeout&gt; | null = null

  const debounced = (...args: Parameters&lt;T&gt;) =&gt; {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() =&gt; {
      fn(...args)
    }, delay)
  }

  debounced.cancel = () =&gt; {
    if (timer) clearTimeout(timer)
    timer = null
  }

  return debounced as T &amp; { cancel: () =&gt; void }
}
</code></pre>
<p>节流函数</p>
<pre><code class="language-javascript">/**
 * 节流函数（throttle）
 * 在一定时间内，只会执行一次目标函数。
 * 适用于滚动触发、窗口缩放、按钮频繁点击等场景。
 *
 * @param func 需要执行的目标函数
 * @param wait 间隔时间（单位：毫秒）
 * @returns 返回包装后的节流函数
 */
export function throttle(func: (...args: any[]) =&gt; void, wait = 300) {
  // 记录上一次执行的时间戳
  let lastTime = 0
  // 定时器，用于 trailing 触发（可选）
  let timer: any = null

  return function (this: any, ...args: any[]) {
    const now = Date.now()

    // 如果当前时间 - 上次执行时间 &gt;= 间隔时间，则立即执行
    if (now - lastTime &gt;= wait) {
      lastTime = now
      func.apply(this, args)
    } else {
      // 清除之前的定时器，重新设置（保证最后一次触发会执行）
      clearTimeout(timer)
      timer = setTimeout(() =&gt; {
        lastTime = Date.now()
        func.apply(this, args)
      }, wait - (now - lastTime))
    }
  }
}
</code></pre>
<p>页面中使用</p>
<pre><code class="language-javascript">import { debounce } from '@/utils/debounce'
import { throttle } from '@/utils/throttle'

const handleChangeDebounce = debounce(需要防抖的Function, time)
const handleChangeThrottle = throttle(需要节流的Function, time)</code></pre>]]></description>
    <pubDate>Tue, 02 Dec 2025 17:26:06 +0800</pubDate>
    <author>suxiaojun</author>
    <guid>https://suxiaojun.top/?post=20</guid>
</item>
<item>
    <title>vue3请求封装</title>
    <link>https://suxiaojun.top/?post=19</link>
    <description><![CDATA[<pre><code class="language-javascript">/**
 * axios setup to use mock service
 */

    import axios from 'axios';
    import { ElMessage } from 'element-plus';
    import { router } from '@/router'
    // 引入i18
    import { i18n } from '@/utils/i18n';

    // 获取浏览器默认语言
    const defaultLanguage = navigator.language;

    // 获取本地存储语言
    const localStorageLanguage = localStorage.getItem('language')
    // 优先使用本地存储语言
    let useLanguage = localStorageLanguage ? localStorageLanguage : defaultLanguage
    let regex = /cn/i

    const service = axios.create({
      baseURL: import.meta.env.VITE_BASE_API, // 所有的请求地址前缀部分
      timeout: 60000 // 请求超时时间(毫秒)
    });

    // 请求拦截
    service.interceptors.request.use(
      (config) =&gt; {
        //设置 token
        config.headers.Authorization = localStorage.getItem('jwt_token'); //token  代表从后台传过来的cookie的令牌
        config.headers['Language'] = regex.test(useLanguage) ? 'cn' : 'en' // 判断浏览器默认语言是否包含CN(表示中国)

        // config.headers['Content-Type'] = 'application/json';
        // config.headers['Content-Type'] = 'application/x-www-form-urlencoded';
        if (config.method === 'get' &amp;&amp; config.params) {
          let url = config.url + '?';
          let keys = Object.keys(config.params);
          for (let key of keys) {
            url += `${key}=${encodeURIComponent(config.params[key])}&amp;`;
          }
          url = url.slice(0, -1);
          config.params = {};
          config.url = url;
        }
        return config;
      },
      (err) =&gt; {
        return Promise.reject(err);
      }
    );
    let isMessageShown = false; // 标识变量，避免多次提示
    // 响应拦截
    service.interceptors.response.use(
      (response) =&gt; {
        const data = response.data;
        // 如果后台返回的报错码和报错信息的话
        if (data.code === 0 || data.code === 200) {
          return data;
        } else if (data.code &gt;= 1500 &amp;&amp; data.code &lt;= 1511) {
          if (!isMessageShown) {
            isMessageShown = true; // 设置标识，避免多次提示
            ElMessage({
              type: 'error',
              message: data.message || data.msg,
              duration: 2000,
              onClose: () =&gt; {
                isMessageShown = false; // 提示结束后恢复标识
              }
            });
          }
          return data;
        } else {
          if (!isMessageShown) {
            isMessageShown = true; // 设置标识，避免多次提示
            ElMessage({
              type: 'error',
              message: data.message || data.msg,
              duration: 2000,
              onClose: () =&gt; {
                isMessageShown = false; // 提示结束后恢复标识
              }
            });
          }
          return data;
        }
      },
      (error) =&gt; {
        if (error.response.status === 401) {
          localStorage.removeItem('jwt_token');
          localStorage.removeItem('userInfo');
          router.push('/auth/login')
          if (!isMessageShown) {
            isMessageShown = true; // 设置标识，避免多次提示
            ElMessage({
              type: 'error',
              message: i18n.global.t('No Login'),
              duration: 2000,
              onClose: () =&gt; {
                isMessageShown = false; // 提示结束后恢复标识
              }
            });
          }
        } else if (error.response.status === 50003) {
          if (!isMessageShown) {
            isMessageShown = true; // 设置标识，避免多次提示
            ElMessage({
              type: 'error',
              message: '服务器维护中',
              duration: 2000,
              onClose: () =&gt; {
                isMessageShown = false; // 提示结束后恢复标识
              }
            });
          }
        } else {
          if (!isMessageShown) {
            isMessageShown = true; // 设置标识，避免多次提示
            ElMessage({
              type: 'error',
              message: error,
              duration: 2000,
              onClose: () =&gt; {
                isMessageShown = false; // 提示结束后恢复标识
              }
            });
          }
        }
      }
    );

    export default service;</code></pre>]]></description>
    <pubDate>Tue, 24 Dec 2024 20:01:54 +0800</pubDate>
    <author>suxiaojun</author>
    <guid>https://suxiaojun.top/?post=19</guid>
</item>
<item>
    <title>vue3+ts封装表格</title>
    <link>https://suxiaojun.top/?post=18</link>
    <description><![CDATA[<p>更新于2025-12-2；<br />
修改</p>
<ol>
<li>新增搜索栏下拉框配置</li>
<li>新增自定义行元素内容或者与行原数据内容并存</li>
<li>新增自定义行内元素样式</li>
</ol>
<p>这里使用了elementplus+vuetify框架封装的</p>
<pre><code class="language-javascript">&lt;template&gt;
  &lt;div&gt;
    &lt;div v-show="showSearch" class="!mb-[8px] !p-6 rounded-b-[16px] bg-[#fff]" :class="className"&gt;
      &lt;!-- 搜索栏 --&gt;
      &lt;el-form :model="form" label-width="auto" :inline="true" label-position="top"&gt;
        &lt;template v-for="(item, index) in TableHeaders" :key="index"&gt;
          &lt;el-form-item :label="item.label" v-if="item.search?.component === 'Input'" class="w-[300px]"&gt;
            &lt;el-input v-model="form[item.field]" :placeholder="item.label" /&gt;
          &lt;/el-form-item&gt;
          &lt;el-form-item :label="item.label" v-if="item.search?.component === 'Select'" class="w-[300px]"&gt;
            &lt;el-select v-model="form[item.field]" :placeholder="item.label" style="min-width: 200px;"
              :filterable="item.search?.componentProps?.filterable || false"&gt;
              &lt;el-option v-for="(optionItem) in item.search?.optionApi?.()" :key="optionItem.label"
                :label="optionItem.label" :value="optionItem.value" /&gt;
            &lt;/el-select&gt;
          &lt;/el-form-item&gt;
          &lt;el-form-item :label="item.label" v-if="item.search?.component === 'DatePicker'"&gt;
            &lt;el-config-provider :locale="zhCn"&gt;
              &lt;el-date-picker v-model="form[item.field]" :type="item.search?.componentProps?.type || 'daterange'"
                range-separator="-" start-placeholder="开始时间" end-placeholder="结束时间"
                size="default" :value-format="item.search?.componentProps?.valueFormat || 'YYYY-MM-DD'"
                :unlink-panels="item.search?.componentProps?.unlinkPanels || false" /&gt;
            &lt;/el-config-provider&gt;
          &lt;/el-form-item&gt;
        &lt;/template&gt;
        &lt;el-form-item style="display: block;" class="!flex flex-1 flex-nowrap !items-end mr-0 min-w-[200px]"&gt;
          &lt;el-button type="primary" color="#5d87ff" class="!text-[#fff]" @click="submit"&gt;
            查询
          &lt;/el-button&gt;
          &lt;el-button @click="handleForm"&gt;重置&lt;/el-button&gt;
          &lt;slot name="searchButton" :props="form" /&gt;
          &lt;el-dropdown v-show="SiftHeader" style="margin-left: auto" trigger="click" :hide-on-click="false"&gt;
            &lt;span class="el-dropdown-link d-flex align-center ga-2"&gt;
              &lt;img :src="filterSVG" width="20px" alt=""&gt;
              &lt;span&gt;配置&lt;/span&gt;
            &lt;/span&gt;
            &lt;template #dropdown&gt;
              &lt;el-dropdown-menu&gt;
                &lt;el-checkbox-group v-model="check" @change="changeCheckbox"&gt;
                  &lt;el-dropdown-item v-for="(item, index) in checkList" :key="index"&gt;
                    &lt;el-checkbox :label="item.label" :value="item"&gt;
                    &lt;/el-checkbox&gt;
                  &lt;/el-dropdown-item&gt;
                &lt;/el-checkbox-group&gt;
              &lt;/el-dropdown-menu&gt;
            &lt;/template&gt;
          &lt;/el-dropdown&gt;
        &lt;/el-form-item&gt;
      &lt;/el-form&gt;
    &lt;/div&gt;
    &lt;div class="!px-4 rounded-[16px] bg-[#fff]"&gt;
      &lt;!-- 表格列表 --&gt;
      &lt;v-data-table-server :height="height" :headers="tableHeaderList" :items="TableData"
        :items-per-page="pagination.itemsPerPage" :page.sync="pagination.page"
        :items-per-page-text="'分页页面文本'" show-current-page
        :page-text="'页面总数文本' + props.Total" :items-per-page-options="perPageOptions"
        :items-length="props.Total" @update:options="switchPagination" :hide-default-footer="HideFooter"&gt;
        &lt;template v-slot:headers="{ columns }: any"&gt;
          &lt;tr&gt;
            &lt;template v-for="column in columns" :key="column.field"&gt;
              &lt;th&gt;
                &lt;div class="mr-2" :style="{ width: column.width ? column.width : '100px' }"&gt;
                  {{ column.label }}
                  &lt;v-tooltip v-if="column.labelTipTitle" location="end" :text="column.labelTipTitle"&gt;
                    &lt;template v-slot:activator="{ props }"&gt;
                      &lt;v-icon v-bind="props" icon="mdi-help-circle-outline" size="small"&gt;&lt;/v-icon&gt;
                    &lt;/template&gt;
                  &lt;/v-tooltip&gt;
                &lt;/div&gt;
              &lt;/th&gt;
            &lt;/template&gt;
          &lt;/tr&gt;
        &lt;/template&gt;
        &lt;template v-slot:item="{ item }"&gt;
          &lt;tr class="table-row"&gt;
            &lt;td v-for="row in tableHeaderList" :key="row.id"&gt;
              &lt;template v-if="row.field !== 'operate'"&gt;
                &lt;div v-if="row.customize" :class="row.itemClass"&gt;
                  &lt;!-- vue3动态创建DOM 元素 --&gt;
                  &lt;component v-if="row?.render" :is="row.render(item, onRenderClick)" @click.stop /&gt;
                &lt;/div&gt;
                &lt;div v-else class="flex items-center gap-2" :class="row.itemClass"&gt;
                  &lt;component v-if="row?.render &amp;&amp; row.renderFloat === 'left'" :is="row.render(item, onRenderClick)"
                    @click.stop /&gt;
                  &lt;span v-show="!row.type"&gt;
                    {{ handleFormatter(item, row) }}
                  &lt;/span&gt;
                  &lt;span v-show="row.type === 'time'"&gt;
                    {{ formatDate(handleFormatter(item, row)) }}
                  &lt;/span&gt;
                  &lt;component v-if="row?.render &amp;&amp; row.renderFloat === 'right'" :is="row.render(item, onRenderClick)"
                    @click.stop /&gt;
                &lt;/div&gt;
              &lt;/template&gt;
              &lt;template v-else&gt;
                &lt;slot name="operate" :item="item" /&gt;
              &lt;/template&gt;
            &lt;/td&gt;
          &lt;/tr&gt;
        &lt;/template&gt;
      &lt;/v-data-table-server&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script setup lang="ts"&gt;
import { onBeforeUnmount, onMounted, reactive, ref, watch, PropType, computed } from 'vue';
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import en from 'element-plus/es/locale/lang/en';
import { formatDate } from "@/utils/formatDate";
import filterSVG from "@/assets/images/admin-backend/filter.svg";

/**
 * @description 可配置相关插槽
 * searchButton 搜索框中按钮插槽
 * operate 表格中操作列中插槽
 */

interface TableHeader {
  field: string; // 字段名
  label: string; // 表头名
  width?: string; // 列宽
  type?: string; // 内容类型
  search?: {
    hidden?: boolean;
    component?: string;
    componentProps?: {
      type?: any; // 时间选择器："date" | "year" | "years" | "month" | "months" | "dates" | "week" | "datetime" | "datetimerange" | "daterange" | "monthrange" | "yearrange"
      unlinkPanels?: boolean;
      valueFormat?: string;
      filterable?: boolean;
    };
    optionApi?: () =&gt; Array&lt;{ label: string; value: any }&gt;;
  }; // 搜索类型
  formatter?: (value: any, row: any) =&gt; any; // 格式化函数
  render?: (row: any, onRenderClick: any) =&gt; any; // 渲染函数
  renderFloat?: string; // 渲染位置
  itemClass?: string; // 行内元素样式
  customize?: boolean; // 是否自定义
  rowClick?: (row: any) =&gt; void; // 处理行
}

/**
 * @param {Array} TableHeaders // 表格表头
 * @param {Array} TableData // 表格数据
 * @param {Boolean} SiftHeader // 是否使用筛选表头
 * @param {Number} Total // 分页总数
 * @structure
 * field: 对应数据的key
 * label: 表头文本
 * labelTip: 表头是否显示提示图标，默认flase
 * labelTipTitle: 表头提示文本
 * type: time  后端返回时间戳时配置类型，为time时转为YYYY-MM-DD hh:mm:ss格式
 * search: {
 *  hidden: 此数据是否使用搜索，默认true
 *  component: Select下拉框  DatePicker时间筛选  Input输入框
 *  componentProps: {
 *   type
 *   unlinkPanels
 *   valueFormat
 *  }
 * }
 * formatter: function
 */
const props = defineProps({
  className: {
    type: String,
    default: undefined
  },
  TableHeaders: {
    type: Array as () =&gt; TableHeader[],
    default: () =&gt; []  // 添加默认值
  },
  TableData: Array as () =&gt; any,
  SiftHeader: {
    type: Boolean,
    default: false
  },
  SiftHeaderTitle: {
    type: String,
    default: ''
  },
  Total: {
    type: Number,
    default: 0
  },
  HideFooter: Boolean,
  onRenderClick: Function as PropType&lt;(row: any, value: any) =&gt; void&gt;,
  height: {
    type: String,
    default: undefined
  },
});

// 默认分页数和数据量
const pagination = reactive({
  page: 1,
  itemsPerPage: 10, // 每页显示的行数
});
const perPageOptions = reactive([
  { value: 10, title: '10' },
  { value: 25, title: '25' },
  { value: 50, title: '50' },
  { value: 100, title: '100' }
]);
/**
 * @description 表格表头
 * 为了自定义表头数据，不使用props中的数据而是重新定义变量接收
 */
const tableHeaderList: any = ref([]);
/* 搜索表单 */
const form: any = reactive({});
/**
 * 显示搜索表单控件
 * 如果表头中存在需要搜索条件则为true
 */
const showSearch = ref(false);
// 筛选默认勾选
const check: any = ref(null);
// 筛选列表
const checkList: any = computed(() =&gt; {
  return props.TableHeaders.map((item: any) =&gt; ({
    field: item.field,
    label: item.label,
  }));
});

/**
 * @description 处理搜索表单和筛选
 */
const handleForm = () =&gt; {
  // 筛选是否设置了hidden
  props.TableHeaders.forEach((item: any) =&gt; {
    if (!item.search.hidden) {
      form[item.field] = ''
      showSearch.value = true
    }
  })
};
/* 多选框发生变化时触发 */
const changeCheckbox: any = (value: string[]) =&gt; {
  // 原数组对比组件中返回的数组，然后进行重组
  const list = props.TableHeaders.map((item: any) =&gt; {
    let data = null;
    value.forEach((row: any) =&gt; {
      if (row.field === item.field) {
        data = item
        return
      };
    });
    return data
  });
  // 剔除掉要隐藏的对象
  tableHeaderList.value = list.filter((row: any) =&gt; {
    return row != null
  });
  if (props.SiftHeader) {
    localStorage.setItem(props.SiftHeaderTitle, JSON.stringify(check.value))
  }
};

watch(
  [() =&gt; props.TableHeaders,
  ([newHeaders, newLocale]) =&gt; {
    // 初始化 checkList
    checkList.value = newHeaders.map((item: any) =&gt; ({
      field: item.field,
      label: item.label,
    }))
    // 获取本地缓存
    const localSiftHeader = JSON.parse(localStorage.getItem(props.SiftHeaderTitle) || '[]')
    if (props.SiftHeader &amp;&amp; localSiftHeader.length &gt; 1) {
      // 使用map筛选出本地缓存存在的数据
      const mapCheck = newHeaders.map((item: any) =&gt; {
        const matchingRow = localSiftHeader.find((row: any) =&gt; row.field === item.field);
        if (matchingRow) {
          return {
            field: item.field,
            label: item.label,
          };
        }
        return null;
      });
      // 去除掉筛选出后的数据里返回的null
      check.value = mapCheck.filter((row: any) =&gt; row !== null);
    } else {
      check.value = newHeaders.map((item: any) =&gt; ({
        field: item.field,
        label: item.label,
      }))
    }
    // 重新调用 changeCheckbox 以应用新的表头
    changeCheckbox(check.value)
  },
  { immediate: true, deep: true }
);

const emit = defineEmits(['onSubmit', 'pagination']);
/**
 * @description 提交搜索表单
 * 父组件使用@onSubmit 绑定事件
 */
const submit = () =&gt; {
  emit('onSubmit', form);
};
/**
 * @description 切换分页
 * 父组件使用@pagination 绑定事件
 */
const switchPagination = (options: any) =&gt; {
  pagination.page = options.page;
  pagination.itemsPerPage = options.itemsPerPage;
  emit('pagination', options);
};

/**
 * @description 处理formatter
 * @param item 列表数据
 * @param row 表头定义对象
 */
const handleFormatter = (item: any, row: any) =&gt; {
  if ('formatter' in row) {
    return row.formatter(item[row.field], item)
  } else {
    return item[row.field]
  }
};

const isMobile = ref(false);
// 判断是否为移动端
const checkDevice = () =&gt; {
  isMobile.value = window.innerWidth &lt;= 768 || /android/i.test(navigator.userAgent) || /iphone|ipod/i.test(navigator.userAgent);
};

onMounted(() =&gt; {
  handleForm();
  // 监听窗口大小变化
  window.addEventListener('resize', checkDevice)
});
// 组件销毁时移除事件监听器
onBeforeUnmount(() =&gt; {
  window.removeEventListener('resize', checkDevice);
});
&lt;/script&gt;

&lt;style lang="scss" scoped&gt;
.table-row {
  transition: background-color 0.3s ease-out;
}

.table-row:hover {
  background-color: #497ff917;
}

::v-deep(.el-form-item__label-wrap) {
  margin-left: 0 !important;
}

::v-deep(.el-input__wrapper.is-focus),
::v-deep(.el-range-editor.is-active),
::v-deep(.el-select__wrapper.is-focused) {
  box-shadow: 0 0 0 1px #5d87ff !important;
}

::v-deep(.el-button:hover) {
  background-color: #5d88ff4b;
  border-color: #5d88ff56;
  color: #5d87ff;
  outline: none;
}

::v-deep(.el-checkbox__inner:hover) {
  border-color: #5d87ff;
}

::v-deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
  background-color: #5d87ff;
  border-color: #5d87ff;
}

::v-deep(.el-checkbox__input.is-checked+.el-checkbox__label) {
  color: #5d87ff;
}
&lt;/style&gt;</code></pre>
<p>下面是使用这个封装组件的父组件<br />
HTML文件</p>
<pre><code class="language-javascript">&lt;OperateTable :TableHeaders="ActivationTableHeaderList" :TableData="ActivationTableDataList"
                :Total="total" @onSubmit="(childParams: any) =&gt; subSearch('Activation')(childParams)"
                @pagination="(childParams: any) =&gt; switchPagination"
                :on-row-click="handleRowClick"&gt;
                &lt;template v-slot:operate="{ item }"&gt;
                  &lt;v-menu open-on-hover :v-model="selectOperate"&gt;
                    &lt;template v-slot:activator="{ props }"&gt;
                      &lt;v-icon icon="mdi-dots-horizontal" start v-bind="props" /&gt;
                    &lt;/template&gt;
                    &lt;v-list class="theme-list"&gt;
                      &lt;v-list-item v-for="(operation, index) in operationList" :key="index" color="primary"
                        :ripple="true" @click="handleAction(operation, item)"&gt;
                        &lt;v-list-item-title class="text-subtitle-1 font-weight-regular"&gt;
                          {{ operation.label }}
                        &lt;/v-list-item-title&gt;
                      &lt;/v-list-item&gt;
                    &lt;/v-list&gt;
                  &lt;/v-menu&gt;
                &lt;/template&gt;
              &lt;/OperateTable&gt;</code></pre>
<p>JS部分</p>
<pre><code class="language-javascript">import { ref, reactive, onMounted, computed, provide, h } from 'vue';
/* 为了使用动态使用icon引入组件 */
import { VIcon } from 'vuetify/components';

/* 列表数据 */
const TableDataList: any = ref([])

/* 搜索查询 */
const getList = async (value: string) =&gt; {
  const data: any = {
    page: tablePage.value,
    size: itemsPerPage.value,
    ...searchFrom.value
  }
  const res: any = await getListApi(data)
  if (res.code !== 0) return
  TableDataList.value = res.data.list
  total.value = res.data.total
}

/* 动态DOM元素的事件 */
const handleRowClick = async (row: any, value: string) =&gt; {
  const params = {
    key: row[value]
  }
  const res: any = await decryptApi(params)
  if (res.code !== 0) return

};
/* 定义表头 */
const TableHeaderList = computed(() =&gt; {
  return ([
    {
      field: 'field',
      label: '不需要搜索',
      width: '130px',
      search: {
        hidden: true
      }
    },
    {
      field: 'field',
      label: '输入框搜索',
      width: '60px',
      search: {
        component: 'Input',
      }
    },
    {
      field: 'time',
      label: '时间格式搜索',
      type: 'time',
      search: {
        component: 'DatePicker',
        componentProps: {
          type: 'daterange',
          unlinkPanels: true,
          valueFormat: 'YYYY-MM-DD'
        }
      }
    },
    {
      field: 'field',
      label: '下拉菜单搜索和根据数据回显',
      width: '130px',
      search: {
        component: 'Select',
        optionApi: () =&gt; {
          const res = [
            {
              value: 0,
              label: '下拉选项1'
            },
            {
              value: 1,
              label: '下拉选项2'
            }
          ]
          return res
        }
      },
      formatter: (cellValue: string | number) =&gt; {
        let typeText: string | number = ''
        switch (cellValue) {
          case 0:
            typeText = '回显数据1'
            break
          case 1:
            typeText = '回显数据1'
            break
          default:
            typeText = cellValue
            break
        }
        return typeText
      }
    },
    {
      field: 'field',
      label: '自定义行元素内容',
      width: '130px',
      search: {
        hidden: true
      },
      customize: true,
      render: (row: any) =&gt; {
        return h(
          'div',
          {},
          `${row.pay_amount} ${row.pay_coin_name}`
        )
      }
    },
    {
      field: 'field',
      label: '在原有行元素内容增加一个自定义内容并配置属性',
      width: '160px',
      search: {
        hidden: true
      },
      renderFloat：left,
      // Vue3特性--动态插入DOM元素
      render: (row: any) =&gt; h(VIcon, { icon: 'mdi-eye-outline', style: 'color: #939a9d; cursor: pointer;', onClick: () =&gt; handleRowClick(row, '') })
    },
    {
      field: 'operate',
      label: t("operate"),
      width: '50px',
      search: {
        hidden: true
      }
    }
  ])
});</code></pre>]]></description>
    <pubDate>Tue, 24 Dec 2024 19:43:15 +0800</pubDate>
    <author>suxiaojun</author>
    <guid>https://suxiaojun.top/?post=18</guid>
</item>
<item>
    <title>NVM安装和使用</title>
    <link>https://suxiaojun.top/?post=17</link>
    <description><![CDATA[<p>一、NVM安装<br />
镜像源<br />
官方源：<a href="https://nodejs.org/dist/">https://nodejs.org/dist/</a><br />
淘宝源：<a href="https://npm.taobao.org/mirrors/node/">https://npm.taobao.org/mirrors/node/</a><br />
NVM 官方源：<a href="https://nodejs.org/download/">https://nodejs.org/download/</a><br />
使用方法：例如：nvm node_mirror <a href="https://nodejs.org/dist/">https://nodejs.org/dist/</a></p>
<p><strong>注意：安装NVM之前，要彻底卸载node.js</strong></p>
<p>NVM下载地址：nvm下载地址：<a href="https://github.com/coreybutler/nvm-windows/releases">https://github.com/coreybutler/nvm-windows/releases</a><br />
<a href="https://suxiaojun.top/content/uploadfile/202408/1e1c1724197632.png"><img src="https://suxiaojun.top/content/uploadfile/202408/thum-1e1c1724197632.png" alt="" /></a><br />
选择：nvm-setup.zip进行下载</p>
<p>第一步：解压.zip压缩包到指定目录<br />
<a href="https://suxiaojun.top/content/uploadfile/202408/ebda1724197667.png"><img src="https://suxiaojun.top/content/uploadfile/202408/ebda1724197667.png" alt="" /></a></p>
<p>第二步：双击nvm-setup.exe开始安装<br />
<a href="https://suxiaojun.top/content/uploadfile/202408/d41b1724197672.png"><img src="https://suxiaojun.top/content/uploadfile/202408/d41b1724197672.png" alt="" /></a></p>
<p>这里是设置nvm的安装路径，注意nvm安装的路径不能有空格或者中文<br />
<a href="https://suxiaojun.top/content/uploadfile/202408/0f491724197681.png"><img src="https://suxiaojun.top/content/uploadfile/202408/thum-0f491724197681.png" alt="" /></a></p>
<p>这里是设置使用nvm安装node时，node的安装路径，防止node没删除干净就最好不要修改node路径，默认路径就好<br />
<a href="https://suxiaojun.top/content/uploadfile/202408/1ca91724197687.png"><img src="https://suxiaojun.top/content/uploadfile/202408/thum-1ca91724197687.png" alt="" /></a></p>
<p>安装完成后，在CMD命令窗口输入nvm -v,输出如下版本号即证明安装成功<br />
<a href="https://suxiaojun.top/content/uploadfile/202408/07c61724197703.png"><img src="https://suxiaojun.top/content/uploadfile/202408/07c61724197703.png" alt="" /></a></p>
<p>修改nvm配置文件<br />
nvm安装完成后，打开nvm安装目录下的settingst文件:<a href="https://suxiaojun.top/content/uploadfile/202408/25b11724197715.png"><img src="https://suxiaojun.top/content/uploadfile/202408/thum-25b11724197715.png" alt="" /></a></p>
<p>将settings改为<br />
root: D:\nvm<br />
path: C:\Program Files\nodejs<br />
arch: 64<br />
proxy: none<br />
node_mirror: <a href="http://npmmirror.com/mirrors/node/">http://npmmirror.com/mirrors/node/</a><br />
npm_mirror: <a href="http://npmmirror.com/mirrors/npm/">http://npmmirror.com/mirrors/npm/</a><br />
<a href="https://suxiaojun.top/content/uploadfile/202408/041a1724197731.png"><img src="https://suxiaojun.top/content/uploadfile/202408/041a1724197731.png" alt="" /></a></p>
<p>二、安装node<br />
查看nvm支持安装的node版本<br />
1、命令：nvm list available</p>
<p>注意：如果镜像源错误如下<br />
nvm list available<br />
Error retrieving &quot;<a href="http://npm.taobao.org/mirrors/node/index.json">http://npm.taobao.org/mirrors/node/index.json</a>&quot;: HTTP Status 404<br />
那么就更换官方镜像源<br />
命令：nvm node_mirror <a href="https://nodejs.org/dist/">https://nodejs.org/dist/</a><br />
<a href="https://suxiaojun.top/content/uploadfile/202408/d6441724197750.png"><img src="https://suxiaojun.top/content/uploadfile/202408/thum-d6441724197750.png" alt="" /></a></p>
<p>使用nvm install 版本号安装指定版本node<br />
比如同时安装：nvm install 18.18.1 和nvm install 15.2.1</p>
<p>切换node版本<br />
初次使用nvm安装node之后，必须先使用nvm use 版本号切换到已安装版本的node才可以<br />
比如：nvm use 18.18.1</p>
<p>查看当前node版本  node -v</p>
<p>下面是查看安装在本机的所有node.js版本，切换node版本和删除node版本<br />
我之前已经装过两个版本的node，这条命令可以查看装的所有node版本<br />
nvm ls</p>
<p>切换到另一个的版本<br />
nvm use 版本号</p>
<p>删除不要的版本，卸载指定版本的nodejs，当安装失败时可以卸载使用<br />
nvm uninstall 版本号</p>
<p>一些nvm其他命令<br />
nvm off // 禁用node.js版本管理(不卸载任何东西)<br />
nvm on // 启用node.js版本管理<br />
nvm -v // 显示nvm版本<br />
nvm install stable // 安装最新稳定版<br />
nvm current // 显示当前使用中的nvm版本</p>]]></description>
    <pubDate>Wed, 21 Aug 2024 07:45:47 +0800</pubDate>
    <author>suxiaojun</author>
    <guid>https://suxiaojun.top/?post=17</guid>
</item>
<item>
    <title>vue请求封装</title>
    <link>https://suxiaojun.top/?post=16</link>
    <description><![CDATA[<p>1.创建环境文件.env.development和.env.production</p>
<pre><code class="language-javascript">// 开发环境
# 页面标题
VUE_APP_TITLE = 'Blog'

# 开发环境配置
ENV = 'development'

# 开发环境
VUE_APP_BASE_API = '开发环境域名'

# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true

// 生产环境
# 页面标题
VUE_APP_TITLE = 'Blog'

# 生产环境配置
ENV = 'production'

# 生产环境
VUE_APP_BASE_API = '生产环境域名'</code></pre>
<p>2.二次封装请求</p>
<pre><code class="language-javascript">// 1.引入 axios
import axios from 'axios';
import { Message } from 'element-ui';
// 引入 cookie
import { getCookie } from './cookie';

// 2.创建 axios 实例
const service = axios.create({
  // 3.配置
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 60 * 1000 //请求的超时时间
})

// 4.请求拦截
service.interceptors.request.use(
  config =&gt; {
      config.baseURL = process.env.VUE_APP_FastAdmin_API;

    //设置 token
    if (localStorage.getItem('token')) {
      config.headers.token = localStorage.getItem('token');//token  代表从后台传过来的token的令牌
    } else {
      config.headers.token = getCookie('token');//token  代表从后台传过来的cookie的令牌
      if (getCookie('token')) {
        localStorage.setItem('Token', getCookie('token'))
      }
    }
    if (config.method === 'get' &amp;&amp; config.params) {
      let url = config.url + '?';
      let keys = Object.keys(config.params);
      for (let key of keys) {
        url += `${key}=${encodeURIComponent(config.params[key])}&amp;`;
      }
      // let url = config.url + '?' + tansParams(config.params);
      url = url.slice(0, -1);
      config.params = {};
      config.url = url
    }
    return config;
  },
  err =&gt; {
    return Promise.reject(err);
  }
)

// 5.响应拦截
service.interceptors.response.use(
  res =&gt; {
    // console.log(res);
    const data = res.data;
    //如果后台返回的报错码和报错信息的话
    if (data.code === 1 || (data.code &gt;= 200 &amp;&amp; data.code &lt;= 299)) {
      return data;
    } else if (data.code == 0) {
      Message({
        type: "error",
        message: data.msg,
        duration: 2000
      });
      return data;
    }
  },
  err =&gt; {
    console.log(err);
    if (err.response.data.code === 401) {
      localStorage.removeItem("UserInfo");
      localStorage.removeItem("myInfo");
      localStorage.removeItem("Token");
      Message({
        type: "error",
        message: '请先登录',
        duration: 2000
      })
    } else if (err.response.data.code === 50003) {
      Message({
        type: "error",
        message: '服务器维护中',
        duration: 2000
      })
    } else {
      Message({
        type: "error",
        message: err,
        duration: 2000
      })
    }
  }
)

// 6.导出
export default service;</code></pre>
<p>3.cookie配置</p>
<pre><code class="language-javascript">// 封装了cookie
import Cookies from "js-cookie";

// 创建cookie
export function setCookie(key, value, expires) {
    return Cookies.set(key, value, { expires: expires })
}

// 获取 cookie
export function getCookie(key) {
    return Cookies.get(key);
}

// 删除cookie
export function removeCookie(key) {
    return Cookies.remove(key);
}</code></pre>]]></description>
    <pubDate>Thu, 25 Jul 2024 10:09:41 +0800</pubDate>
    <author>suxiaojun</author>
    <guid>https://suxiaojun.top/?post=16</guid>
</item>
<item>
    <title>使用element-plus表格合并</title>
    <link>https://suxiaojun.top/?post=14</link>
    <description><![CDATA[<p>可以使用element-plus的在线编辑查看效果</p>
<pre><code class="language-javascript">&lt;template&gt;
  &lt;div&gt;
    &lt;el-table
      :data="tableData"
      :span-method="objectSpanMethod"
      border
      style="width: 100%; margin-top: 20px"
    &gt;
      &lt;el-table-column prop="week" label="ID" width="180" /&gt;
      &lt;el-table-column prop="startTime" label="Name" /&gt;
      &lt;el-table-column prop="endTime" sortable label="Amount 1" /&gt;
      &lt;el-table-column prop="status" sortable label="Amount 2" /&gt;
      &lt;el-table-column prop="doorStatus" sortable label="Amount 3" /&gt;&lt;el-table-column prop="week" label="ID" width="180" /&gt;
      &lt;el-table-column prop="startTime" label="Name" /&gt;
      &lt;el-table-column prop="endTime" sortable label="Amount 1" /&gt;
      &lt;el-table-column prop="status" sortable label="Amount 2" /&gt;
      &lt;el-table-column prop="doorStatus" sortable label="Amount 3" /&gt;
    &lt;/el-table&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script lang="ts" setup&gt;
import type { TableColumnCtx } from 'element-plus'

interface SpanMethodProps {
  row: User
  column: TableColumnCtx&lt;User&gt;
  rowIndex: number
  columnIndex: number
}

const objectSpanMethod = ({
  row,
  column,
  rowIndex,
  columnIndex,
}: SpanMethodProps, ) =&gt; {
  if (columnIndex === 0) {
      const r = compareSingleAttrReturnColSpan(row, 'week', rowIndex, tableData);
      return r;
  }
  if (columnIndex === 1) {
      const r = compareSingleAttrReturnColSpan(row, 'startTime', rowIndex, tableData);
      return r;
  }
}

const tableData: any[] = [
  {
          "week": "1", //周一
          "doorStatus": "2", //门状态模式（梯控模式），0-普通状态，2-常开状态（自由），3-常闭状态（禁用）
          "startTime": "8:15:00", //开始时间
          "endTime": "18:30:00", //结束时间
          "status": "1" //是否开启，1-开启，0-关闭
  },
  {
          "week": "1",
          "doorStatus": "3",
          "startTime": "8:15:00",
          "endTime": "14:41:00",
          "status": "1"
  },
  {
          "week": "1",
          "doorStatus": "2",
          "startTime": "14:41:00",
          "endTime": "14:49:00",
          "status": "1"
  },
  {
          "week": "2",
          "doorStatus": "2",
          "startTime": "8:15:00",
          "endTime": "14:39:00",
          "status": "1"
  },
  {
          "week": "2",
          "doorStatus": "3",
          "startTime": "14:39:00",
          "endTime": "14:41:00",
          "status": "1"
  },
  {
          "week": "2",
          "doorStatus": "2",
          "startTime": "14:41:00",
          "endTime": "14:49:00",
          "status": "1"
  }]
  // 处理合并
  const compareSingleAttrReturnColSpan=(row, compareAttr, index, tableData)=&gt; {
  console.log('index',index)
    let count = 0;
    let start = index;
    let multCompare = true;

    while (start &lt; tableData.length &amp;&amp; multCompare) {
      start++;
      count++;
      multCompare = row[compareAttr] === tableData[start]?.[compareAttr];//依旧符合相等条件
    }
    start--;//退出循环后的 新起始行 index
    console.log(start, count)
    if (start &lt; tableData.length) {
      tableData[index][`${compareAttr}_count`] = count;//将合并的行数存储在行数据中的一个特定属性中
    }

    if (index &gt; 0 &amp;&amp; count + 1 === tableData[index - 1][`${compareAttr}_count`]) {
      return [0, 0];//不需要合并
    }
    return [count, 1];
  }
&lt;/script&gt;
</code></pre>]]></description>
    <pubDate>Tue, 16 Jul 2024 16:22:11 +0800</pubDate>
    <author>suxiaojun</author>
    <guid>https://suxiaojun.top/?post=14</guid>
</item>
<item>
    <title>小程序请求二次封装</title>
    <link>https://suxiaojun.top/?post=13</link>
    <description><![CDATA[<p>创建config文件，将配置文件放入一个目录中, 目录结构如下</p>
<pre><code>config/
    ├── index.js  # 入口文件
    ├── env.js    # 环境获取
    ├── dev.js    # 开发环境配置
    └── pro.js    # 生产环境配置</code></pre>
<p>index.js</p>
<pre><code class="language-javascript">/**
 * index
 */

import { getEnv } from './env.js';
import dev from './dev.js';
import pro from './pro.js';

const env = getEnv();
console.log('env', env);

let config = pro;

if (env == 'dev') {
  config = dev;
}

export default config;
</code></pre>
<p>env.js  使用微信小程序API<a href="https://developers.weixin.qq.com/miniprogram/dev/api/open-api/account-info/wx.getAccountInfoSync.html" title="wx.getAccountInfoSync()">wx.getAccountInfoSync()</a>获取小程序当前的环境</p>
<pre><code class="language-javascript">/**
 * env
 */

export function getEnv() {
  // let res = __wxConfig.envVersion; // 文档已经没了，但是还可以用，不知道啥时候会弃用!!!
  /* 
   * 获取当前账号信息
   * miniProgram {
   *  appId, envVersion, plugin
   * }
   **/
  let res = wx.getAccountInfoSync();
  // 客户端中host有值，本地开发的时候host为null
  if (res.miniProgram.envVersion == 'develop' || res.miniProgram.envVersion == 'trial') {
    return 'dev';
  } else {
    return 'pro';
  }
}</code></pre>
<p>dev.js</p>
<pre><code class="language-javascript">/**
 * dev
 */
export default {
  BASE_URL: '本地域名', // http://127.0.0.1
};
</code></pre>
<p>pro.js</p>
<pre><code class="language-javascript">/**
 * pro
 */
export default {
  'BASE_URL': '线上域名', // https://www.baidu.com
}
</code></pre>
<p>接下来先随便封装请求后续再优化，先引入自己定义的config环境变量</p>
<pre><code class="language-javascript">// 引入环境变量
import config from '../config/index';
// 获取域名，小程序的域名需要在小程序后台配置相同的域名
const baseUrl = config.BASE_URL;</code></pre>
<p>封装<a href="https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html" title="wx.request">wx.request</a>请求</p>
<pre><code class="language-javascript">module.exports = {
  /**
   * 二次封装wx.request
   * url:请求的接口地址
   * method:请求方式 GET,POST....
   * data:要传递的参数
   */
  request: (url, method, data) =&gt; {
    //这里使用ES6的写法拼接的字符串
    let _url = `${baseUrl}/${url}`;
    return new Promise((resolve, reject) =&gt; {
      wx.showLoading({
        title: '正在加载',
      });
      wx.request({
        url: _url,
        data: data,
        method: method,
        header: {
          'content-type': 'application/json',
          'token': wx.getStorageSync('Token')
        },
        success: (res) =&gt; {
          let {
            code
          } = res.data;
          if (code === 200 || code === 1) {
            resolve(res.data);
            wx.hideLoading();
          } else {
            console.log(res);
            wx.showToast({
              title: res.data.data,
              icon: "none",
              duration: 2000
            })
          }
        },
        fail() {
          reject('服务器连接中断')
        }
      });

    });
  },
}</code></pre>]]></description>
    <pubDate>Tue, 07 May 2024 15:04:33 +0800</pubDate>
    <author>suxiaojun</author>
    <guid>https://suxiaojun.top/?post=13</guid>
</item>
<item>
    <title>简单弄一个format</title>
    <link>https://suxiaojun.top/?post=12</link>
    <description><![CDATA[<p>/**</p>
<ul>
<li>@description 格式化时间</li>
<li>@param {number/String} timestamp 时间戳（毫秒）</li>
<li>@param {string} formatString 想要返回的格式</li>
<li>
<p>@returns {string} - 格式化后的日期字符串<br />
*/<br />
export const format = (timestamp, formatString) =&gt; {<br />
const date = new Date(timestamp);<br />
const year = date.getFullYear();<br />
const month = padZero(date.getMonth() + 1); // 月份是从0开始的，所以需要加1<br />
const day = padZero(date.getDate());<br />
const hours = padZero(date.getHours());<br />
const minutes = padZero(date.getMinutes());<br />
const seconds = padZero(date.getSeconds());</p>
<p>// 填充前导零的函数<br />
function padZero(number) {<br />
return number.toString().padStart(2, '0');<br />
}</p>
<p>// 替换格式字符串中的占位符<br />
return formatString<br />
.replace('yyyy', year)<br />
.replace('MM', month)<br />
.replace('dd', day)<br />
.replace('hh', hours)<br />
.replace('mm', minutes)<br />
.replace('ss', seconds);</p>
</li>
</ul>
<p>}</p>
<p>第二种方法<br />
// utils/formatDate.js<br />
export function formatDate(timestamp) {<br />
const date = new Date(timestamp);<br />
const year = date.getFullYear();<br />
const month = String(date.getMonth() + 1).padStart(2, '0');<br />
const day = String(date.getDate()).padStart(2, '0');<br />
const hours = String(date.getHours()).padStart(2, '0');<br />
const minutes = String(date.getMinutes()).padStart(2, '0');<br />
const seconds = String(date.getSeconds()).padStart(2, '0');</p>
<p>return <code>${year}-${month}-${day} ${hours}:${minutes}:${seconds}</code>;<br />
}</p>]]></description>
    <pubDate>Tue, 19 Mar 2024 10:41:18 +0800</pubDate>
    <author>suxiaojun</author>
    <guid>https://suxiaojun.top/?post=12</guid>
</item>
<item>
    <title>抖音无水印视频</title>
    <link>https://suxiaojun.top/?post=11</link>
    <description><![CDATA[<p>1.复制抖音视频地址<br />
例：<a href="https://v.douyin.com/XXXX/">https://v.douyin.com/XXXX/</a><br />
2.浏览器切换手机模式请求这个视频地址，然后找到 <a href="https://www.iesdouyin.com/web/api/v2/aweme/iteminfo">https://www.iesdouyin.com/web/api/v2/aweme/iteminfo</a> 这个请求<br />
3.找到一下地址<br />
<a href="https://suxiaojun.top/content/uploadfile/202307/1e671690278663.png"><img src="https://suxiaojun.top/content/uploadfile/202307/thum-1e671690278663.png" alt="" /></a><br />
4.拿到的这个地址是有水印的，把地址上的/palywm的wm去掉后请求则就是无水印的视频了<br />
<a href="https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300fg10000civj393c77uaarcdv9j0&amp;ratio=720p&amp;line=0">https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300fg10000civj393c77uaarcdv9j0&amp;ratio=720p&amp;line=0</a></p>]]></description>
    <pubDate>Tue, 25 Jul 2023 17:46:39 +0800</pubDate>
    <author>suxiaojun</author>
    <guid>https://suxiaojun.top/?post=11</guid>
</item></channel>
</rss>