/* 工具函数 */ import router from '@/router'; import * as CryptoJS from 'crypto-js'; import pick from 'lodash/pick'; import pickBy from 'lodash/pickBy'; import type { createIssueReqType } from '@/api/issue/types'; import type { RepoItemResData } from '@/utils/types'; import DOMPurify from 'dompurify'; import { Message } from 'vue-devui/message'; import qs from 'qs'; /** * 返回 axios 的 data, 或者 obj 最内层的 data */ export const escapeResData = (obj: object) => { if (!obj) return obj; if (!Object.prototype.hasOwnProperty.call(obj, 'data')) return obj; if (obj?.headers?.constructor?.name === 'AxiosHeaders') { return obj?.data; }; return escapeResData(obj?.data); }; export function getRepoName(): string { // return router.currentRoute.value.params.repoName; }; /** * Parse the time to string * @param {(Object|string|number)} time * @param {string} cFormat * @returns {string | null} */ function parseTime(time: string | number | Date, cFormat?: string) { if (!time) { return null; } const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'; let date; if (typeof time === 'object') { date = time; } else { if ((typeof time === 'string')) { if ((/^[0-9]+$/.test(time))) { time = parseInt(time); } else { // support safari time = time.replace(new RegExp(/-/gm), '/'); } } if ((typeof time === 'number') && (time.toString().length === 10)) { time = time * 1000; } date = new Date(time); } const formatObj = { y: date.getFullYear(), m: date.getMonth() + 1, d: date.getDate(), h: date.getHours(), i: date.getMinutes(), s: date.getSeconds(), a: date.getDay() }; const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { const value = formatObj[key]; // Note: getDay() returns 0 on Sunday if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value]; } return value.toString().padStart(2, '0'); }); return time_str; } export function fileToBlob(file: any) { // 创建 FileReader 对象 const reader = new FileReader(); return new Promise(resolve => { // FileReader 添加 load 事件 reader.addEventListener('load', (e) => { let blob; if (typeof e.target.result === 'object') { blob = new Blob([e.target.result]); } else { blob = e.target.result; } resolve(blob); }); // FileReader 以 ArrayBuffer 格式 读取 File 对象中数据 reader.readAsArrayBuffer(file); }); } // 图片转 base64 export function imageToBase64(file: File) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); } export function generateHash(userName: string, salt: string): string { const hashBytes = CryptoJS.SHA256(userName + salt); return hashBytes.toString(CryptoJS.enc.Hex); } export function getFilePath(hash: string, filetype: string): string { const chars: string[] = hash.split(''); const sb: string[] = []; for (const c of chars) { if (isNaN(Number(c))) { sb.push(c); } } return `${sb.slice(0, 2).join('')}/${sb.slice(2, 4).join('')}/${hash}.${filetype}`; } export function getImageUrl(iam_id?: string, fileType: string = 'png', isAvatar: boolean = true) { if (!iam_id) { const accountInfo = JSON.parse(localStorage.getItem('opUserInfo') || '{}'); iam_id = accountInfo.iam_id; } if (isAvatar) { const host = 'https://gitcode-img.obs.cn-south-1.myhuaweicloud.com:443/'; const salt = 'avatar'; const hash = generateHash(iam_id || '', salt); const path = getFilePath(hash, fileType || 'png'); return host + path + '?time=' + new Date().getTime(); } } /** * 处理issue put 数据 */ export function formatIssuePutData(data: createIssueReqType) { const params = pick(data, ['title', 'description', 'confidential', 'discussions', 'assignee', 'assignee_id', 'assignee_ids', 'project_id', 'issue_iid', 'labels', 'milestone_id', 'discussion_locked', 'state_event']); let { labels } = params; if (typeof labels === 'object') { labels = labels.map(item => { if (typeof item === 'string') { return item; } else if (typeof item.name === 'string') { return item.name; } else { return null; } }); } return ({ ...params, labels, issue_category: '-', issue_stage: '-', issue_severity: '-' }); } /** * 提取展示的姓名 */ export const pickNickName = (author?: any): string => { return author?.nick_name || author?.nickname || author?.name_cn || author?.name || author?.username || ''; }; /** * 去除对象中的空值 */ export function removeEmptyValue(params: object): object { return pickBy(params, (e) => (e !== undefined && e !== '' && e !== null)); } /** * 替换对象中的null为 undefined */ export function replaceNull(obj: {[name:string]: any}): any { for (const k in obj) { if (obj[k] === null) { obj[k] = undefined; } } return obj; } /** * 处理多选数据 1.空值 清空select 2.重复项取消 3.未选中的添加 */ export function formatSelectedData(current: string | object | undefined | null, selectedList: any[], judge: (a: { [name: string]: unknown }, b: { [name: string]: unknown } | unknown) => boolean): object[] { if (!current) return []; const index = selectedList.findIndex((item) => { if (typeof item === 'string') { return item === current; } else { return judge && judge(item, current); } }); if (index > -1) { selectedList.splice(index, 1); } else { selectedList.push(current); } return selectedList; } /** * 处理引用回复文本 */ export const formatQuoteReply = (str: string) => { str = str.trim(); if (!str) return ''; str = str.split('\n').map(e => '>' + e).join('\n') + '\n\n'; return str; }; /** * 判断父级元素有没有类名 */ export function hasClassInParent(target: Element, classNameList: string[]): boolean { if (target?.parentElement) { if (classNameList.every(name => target?.classList?.contains(name))) { return true; } else { return hasClassInParent(target?.parentElement, classNameList); } } else { return target ? classNameList.every(name => target?.classList?.contains(name)) : false; } } /** * 优先展示已经选择的项,已勾选的排在前面 */ export const sortList = (list: Object[] = [], select: Object[] = []):Object[] => { list = list.slice(0); select = select.slice(0); if (select[0]) { const sList:any[] = []; for (let i = 0; i < select.length; i++) { const index = list.findIndex(e => e.value === select[i]?.value); if (index > -1) { const one = list.splice(index, 1)[0]; sList.push(one); } } return sList.concat(list); } else { return list; } }; /** * 处理repo数据便于使用repoItem组件 */ export const repoDataHandler = (data: RepoItemResData[]) => { return data.filter((item) => { return !!item && (item.id || item.resource_id); }).map((item) => { const langs = (item.main_repository_language || []).filter((lang) => { return !!lang; }); return { id: item.id || item.resource_id || '', imgSrc: '', title: item.name || '-', desc: item.description || '-', isStar: item.starred || false, tag: item.visibility || '', to: `/${item.namespace}`, web_url: item.web_url, iconHandleList: [ { icon: 'icon-dot-status', value: langs.join(',') || '-', type: 'language', iconColor: 'red', label: '', to: '' }, { icon: 'gt-star', value: item.star_count, label: '', to: '' }, { icon: 'gt-fork', value: item.forks_count, label: '', to: '' }, { icon: 'gt-date', value: item.last_activity_at, label: '', to: '' } ] }; }); }; /** * 文案关键词高亮 */ export const highlightWords = (Word: string, title?: string) => { if (!Word) return xssPurify(title || ''); title = title ? xssPurify(title) : ''; const regexPattern = new RegExp(`(${Word})`, 'gi'); const str = title?.replace(regexPattern, (_, match) => `${match}`); return str; }; /* 过滤对象中的空属性值 * @param obj * @returns */ export function filterEmptyObj(obj: object) { const newObj = obj; for (const key in newObj) { // 删除空属性值 if (Object.prototype.hasOwnProperty.call(newObj, key)) { if (!newObj[key]) { delete newObj[key]; } } } return newObj; } export const fullscreen = (id: string): void => { if (document.fullscreenElement || document.webkitCurrentFullScreenElement) { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.mozCancelFullScreen) { // 兼容Firefox document.mozCancelFullScreen(); } else if (document.webkitExitFullscreen) { // 兼容Chrome, Safari and Opera等 document.webkitExitFullscreen(); } else if (document.msExitFullscreen) { // 兼容IE/Edge document.msExitFullscreen(); } } else { const dom = document.getElementById(id); if (dom?.requestFullscreen) { dom.requestFullscreen(); } else if (dom?.mozRequestFullScreen) { // 兼容Firefox dom.mozRequestFullScreen(); } else if (dom?.webkitRequestFullScreen) { // 兼容Chrome, Safari and Opera等 dom.webkitRequestFullScreen(); } else if (dom?.msRequestFullscreen) { // 兼容IE/Edge dom.msRequestFullscreen(); } } }; // 对象深层递归合并 export const deepMerge = function(target: object, source: object) { for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { if (source[key] instanceof Object && key in target && target[key] instanceof Object) { deepMerge(target[key], source[key]); } else { target[key] = source[key]; } } } return target; }; // 最长公共子序列(模糊匹配) export const longestCommonSubsequence = function(str1: string, str2: string) { const m = str1.length; const n = str2.length; const dp = new Array(m + 1); for (let i = 0; i <= m; i++) { dp[i] = new Array(n + 1).fill(0); } for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { if (str1[i - 1] === str2[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } let lcs = ''; let i = m; let j = n; while (i > 0 && j > 0) { if (str1[i - 1] === str2[j - 1]) { lcs = str1[i - 1] + lcs; i--; j--; } else if (dp[i - 1][j] > dp[i][j - 1]) { i--; } else { j--; } } return lcs; }; export const blurMatch = function(target: string, keywords: string) { const m = target.length; const n = keywords.length; const dp = new Array(m + 1); for (let i = 0; i <= m; i++) { dp[i] = new Array(n + 1).fill(''); } const match = []; for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { if (target[i - 1] === keywords[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + keywords[j - 1]; if (typeof match[j - 1] === 'undefined') match[j - 1] = i - 1; } else { dp[i][j] = dp[i - 1][j].length > dp[i][j - 1].length ? dp[i - 1][j] : dp[i][j - 1]; } } } return match; }; /** * xss 过滤 * purify 规则参考:https://github.com/cure53/DOMPurify#control-our-allow-lists-and-block-lists */ const purifyConfig = { FORBID_TAGS: ['img'] }; export const xssPurify = (str: string) => DOMPurify.sanitize(str, purifyConfig); export const messageError = ({ error_message, message }) => Message.error(error_message || message); export const savePageRef = (fullPath:string) => { // const BASE_URL = (import.meta as any).env.VITE_HOST; const BASE_URL = window.location.origin + '/openRepoPortal'; const beforeRef = sessionStorage.getItem('ref'); const ref = beforeRef || document.referrer || ''; window.page_ref = ref; sessionStorage.setItem('ref', BASE_URL + fullPath); }; /** * 处理上报header中的地址,防止 header size 过大 报错431 * @param href string访问地址 * @returns {string} 访问地址 */ export const cutParamsInUrl = (href: string) => { if (!href) return ''; try { const location = new URL(href); const query = qs.parse(location.search, { ignoreQueryPrefix: true }); for (const k in query) { if (typeof query[k] === 'string') { query[k] = query[k]?.substring(0, 100); } } location.search = qs.stringify(query); return location.href; } catch (error) { console.log(error); return href; }; }; /** * 获取注册来源utm_source(有效时间一小时) * @returns {string} utm_source */ export const getSignUtmSource = () => { const utm_source_sign = localStorage.getItem('utm_source_sign'); if (utm_source_sign) { const intervalTime = 1000 * 60 * 60; const currentTime = Date.now(); const utm_source_sign_time = Number(localStorage.getItem('utm_source_sign_time')); if (utm_source_sign_time + intervalTime < currentTime) { // 超过一小时,清除utm_source localStorage.removeItem('utm_source_sign'); localStorage.removeItem('utm_source_sign_time'); return ''; } else { return JSON.parse(utm_source_sign); } } return ''; };