diff --git a/.env.development b/.env.development index ff3d318799af25d42f067c83d9ab04af89299487..d10989f603dcf07bc8783c3701ba66e6499bd2a3 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,4 @@ VITE_XSRF_COOKIE_NAME = '_U_T_' -VITE_XSRF_HEADER_NAME = 'Token' \ No newline at end of file +VITE_XSRF_HEADER_NAME = 'Token' +VITE_COOKIE_DOMAIN = localhost +VITE_COOKIE_VER = 20240830 diff --git a/components.d.ts b/components.d.ts index db2ada213dc520968dedae4627b98d5049135f0a..72dc207afc3846e75cc19409cd4a5e28fe501811 100644 --- a/components.d.ts +++ b/components.d.ts @@ -19,6 +19,7 @@ declare module 'vue' { AppTableToggle: typeof import('./src/components/AppTableToggle.vue')['default'] CBreadcrumb: typeof import('./src/components/collaboration/CBreadcrumb.vue')['default'] ContentWrapper: typeof import('./src/components/ContentWrapper.vue')['default'] + CookieNotice: typeof import('./src/components/CookieNotice.vue')['default'] DetailAside: typeof import('./src/components/detail/DetailAside.vue')['default'] DetailBasicInfo: typeof import('./src/components/detail/DetailBasicInfo.vue')['default'] DetailHeader: typeof import('./src/components/detail/DetailHeader.vue')['default'] diff --git a/src/App.vue b/src/App.vue index c58b94339a6c4d205e19c55df71693405c84465c..5813359a3e4afa963e1da029355ae3a866760153 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,7 +5,6 @@ import { RouterView } from 'vue-router'; import { OScroller, OConfigProvider } from '@opensig/opendesign'; import zhCN from '@opensig/opendesign/es/locale/lang/zh-cn'; import enUS from '@opensig/opendesign/es/locale/lang/en-us'; -import { BAIDU_HM } from '@/data/config'; import { useLangStore, useViewStore } from '@/stores/common'; import { useLocale } from '@/composables/useLocale'; @@ -13,11 +12,11 @@ import { useLocale } from '@/composables/useLocale'; import AppHeader from '@/components/header/AppHeader.vue'; import AppFooter from '@/components/AppFooter.vue'; import GlobalFeedback from './components/GlobalFeedback.vue'; +import CookieNotice from './components/CookieNotice.vue'; const langStore = useLangStore(); const viewState = useViewStore(); const { locale } = useI18n(); - watch( () => langStore.lang, (val) => { @@ -27,18 +26,6 @@ watch( // -------------------- 组件国际化 -------------------- const { isZh } = useLocale(); - -// -------------------- 埋点 -------------------- -const initSensor = () => { - // 百度统计 - (function () { - const hm = document.createElement('script'); - hm.src = BAIDU_HM; - const s = document.getElementsByTagName('HEAD')[0]; - s.appendChild(hm); - })(); -}; -initSensor(); diff --git a/src/components/CookieNotice.vue b/src/components/CookieNotice.vue new file mode 100644 index 0000000000000000000000000000000000000000..9d4ab13b304ffedf9be69a4d613a8ad145c9648c --- /dev/null +++ b/src/components/CookieNotice.vue @@ -0,0 +1,399 @@ + + + + + diff --git a/src/i18n/cookie/cookie-en.ts b/src/i18n/cookie/cookie-en.ts new file mode 100644 index 0000000000000000000000000000000000000000..716aceb422cc1276c18c942ab5d1456c9896f8e5 --- /dev/null +++ b/src/i18n/cookie/cookie-en.ts @@ -0,0 +1,17 @@ +export default { + title: 'openEuler Community Respects Your Privacy.', + desc: 'his site uses cookies from us and our partners to improve your browsing experience and make the site work properly. By clicking "Accept All", you consent to the use of cookies. By clicking "Reject All", you disable the use of unnecessary cookies. You can manage your cookie settings by clicking "Manage Cookies". For more information or to change your cookie settings, please refer to our ', + link: 'About Cookies', + acceptAll: 'Accept All', + rejectAll: 'Reject All', + manage: 'Manage Cookies', + necessaryCookie: 'Strictly Necessary Cookies', + necessaryCookieTip: 'Always active', + necessaryCookieDetail: + 'These cookies are necessary for the site to work properly and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as logging in or filling in forms. You can set the browser to block these cookies, but that can make parts of the site not work. These cookies do not store any personally identifiable information.', + analyticalCookie: 'Analytics Cookies', + analyticalCookieDetail: + 'We will use these cookies only with your consent. These cookies help us make improvements by collecting statistics such as the number of visits and traffic sources.', + saveSetting: 'Save and Accept', + allowAll: 'Accept All', +}; diff --git a/src/i18n/cookie/cookie-zh.ts b/src/i18n/cookie/cookie-zh.ts new file mode 100644 index 0000000000000000000000000000000000000000..d9d1c58582e7ee0a242465873b0e406e07b608bc --- /dev/null +++ b/src/i18n/cookie/cookie-zh.ts @@ -0,0 +1,17 @@ +export default { + title: 'openEuler社区重视您的隐私', + desc: '我们在本网站上使用Cookie,包括第三方Cookie,以便网站正常运行和提升浏览体验。单击“全部接受”即表示您同意这些目的;单击“全部拒绝”即表示您拒绝非必要的Cookie;单击“管理Cookie”以选择接受或拒绝某些Cookie。需要了解更多信息或随时更改您的Cookie首选项,请参阅我们的', + link: '《关于cookies》', + acceptAll: '全部接受', + rejectAll: '全部拒绝', + manage: '管理Cookie', + necessaryCookie: '必要Cookie', + necessaryCookieTip: '始终启用', + necessaryCookieDetail: + '这些Cookie是网站正常工作所必需的,不能在我们的系统中关闭。它们通常仅是为了响应您的服务请求而设置的,例如登录或填写表单。您可以将浏览器设置为阻止Cookie来拒绝这些Cookie,但网站的某些部分将无法正常工作。这些Cookie不存储任何个人身份信息。', + analyticalCookie: '统计分析Cookie', + analyticalCookieDetail: + '我们将根据您的同意使用和处理这些非必要Cookie。这些Cookie允许我们获得摘要统计数据,例如,统计访问量和访问者来源,便于我们改进我们的网站。', + saveSetting: '保存并接受', + allowAll: '全部接受', +}; diff --git a/src/i18n/cookie/index.ts b/src/i18n/cookie/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..a92f0d20f74183595cb1bcdcc6379de062e849bd --- /dev/null +++ b/src/i18n/cookie/index.ts @@ -0,0 +1,7 @@ +import zh from './cookie-zh'; +import en from './cookie-en'; + +export default { + zh, + en, +}; diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 3d78515d23aa8246a4f3f81260abd5cdac1489de..21b31a37b076f84e270fc280f3c16f8c040cc361 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -5,6 +5,7 @@ import software from './software'; import upstream from './upstream'; import detail from '@/i18n/detail'; import collaboration from '@/i18n/collaboration'; +import cookie from './cookie'; const messages = { zh: { @@ -13,6 +14,7 @@ const messages = { upstream: upstream.zh, detail: detail.zh, collaboration: collaboration.zh, + cookie: cookie.zh, }, en: { common: common.en, @@ -20,6 +22,7 @@ const messages = { upstream: upstream.en, detail: detail.en, collaboration: collaboration.en, + cookie: cookie.en, }, }; diff --git a/src/main.ts b/src/main.ts index f67547ce3fd139127dc84cd1421c737ff37d1101..6cc0bdfd4246ebff473cb53cf9411ce410f41ec5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,13 +19,9 @@ import { initRound } from '@opensig/opendesign'; import App from './App.vue'; import router from './router'; import i18n from './i18n'; -import { enableOA, reportPerformance } from './shared/analytics'; initRound('pill'); -enableOA(); -reportPerformance(); - const app = createApp(App); // 国际化 diff --git a/src/router/index.ts b/src/router/index.ts index fbd5e37a7f2dc2c2ab29c0dba88dd4e01d0ecaa9..5c10c9ba2c97b0d5f64dba16b3189ca90a4eac40 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -6,7 +6,6 @@ import { tryLogin } from '@/shared/login'; import { useLoginStore, useUserInfoStore } from '@/stores/user'; import { getCollaborationPermissions } from '@/api/api-collaboration'; import { COLLABORATIONPERMISSION } from '@/data/query'; -import { reportPV } from '@/shared/analytics'; const routes = [ { @@ -147,6 +146,7 @@ router.beforeEach(async (to) => { } await tryLogin(); + loginStore.loginStatusChecked = true; // 协作平台权限 if ((userInfoStore.platformMaintainerPermission === null || userInfoStore.platformAdminPermission === null) && loginStore.isLogined) { @@ -172,16 +172,7 @@ router.beforeEach(async (to) => { return true; }); -router.beforeEach((to, from) => { - if (to.name !== 'notFound' && from.path !== '/' && to.path !== from.path) { - to.meta.$referrer = window.location.href; - } -}); - -router.afterEach((to, from) => { - if (to.name !== 'notFound' && to.path !== from.path) { - reportPV(to.meta.$referrer as string); - } +router.afterEach(() => { useViewStore().$patch({ notFoundPage: false }); }); diff --git a/src/shared/analytics/index.ts b/src/shared/analytics/index.ts index 087e3c4072d07c8dcadc5a7237f19eef6cf5e150..fc1c64b86cf3953fd67f776ed52184df955df5d5 100644 --- a/src/shared/analytics/index.ts +++ b/src/shared/analytics/index.ts @@ -1,21 +1,62 @@ import { OpenAnalytics, OpenEventKeys, getClientInfo } from '@opensig/open-analytics'; import { reportAnalytics } from '@/api/api-analytics'; import type { Awaitable } from '@vueuse/core'; +import { COOKIE_AGREED_STATUS, useCookieStore } from '@/stores/common'; +import router from '@/router'; export const oa = new OpenAnalytics({ appKey: 'openEuler', request: (data) => { + if (useCookieStore().getUserCookieStatus() !== COOKIE_AGREED_STATUS.ALL_AGREED) { + return disableOA(); + } reportAnalytics(data); }, }); +let reportPerfCount = 0; +let routerGuardCancelFuncs: (() => void)[]; + +const setupRouterGuards = () => { + router.isReady().then(() => { + reportPV(); + (routerGuardCancelFuncs ??= []).push( + router.beforeEach((to, from) => { + if (from.path === '/' || to.path === from.path) { + return; + } + to.meta.$referrer = window.location.href; + }), + router.afterEach((to, from) => { + if (to.path === from.path) { + return; + } + reportPV(to.meta.$referrer as string); + }) + ); + }); +}; + export const enableOA = () => { oa.setHeader(getClientInfo()); oa.enableReporting(true); + setupRouterGuards(); + if (reportPerfCount >= 1) { + return; + } + reportPerfCount++; + reportPerformance(); }; export const disableOA = () => { oa.enableReporting(false); + if (routerGuardCancelFuncs) { + routerGuardCancelFuncs.forEach((item) => item()); + routerGuardCancelFuncs = []; + } + ['oa-openEuler-client', 'oa-openEuler-events', 'oa-openEuler-session'].forEach((key) => { + localStorage.removeItem(key); + }); }; /** @@ -33,6 +74,9 @@ export const oaReport = >( eventOptions?: any; } ) => { + if (!oa.enabled) { + return; + } return oa.report( event, async () => ({ diff --git a/src/shared/login.ts b/src/shared/login.ts index 07c04a448da022f68abc04b8c20d17503123e045..6158397547ac3b004bec4ceda00b5df7a07c039a 100644 --- a/src/shared/login.ts +++ b/src/shared/login.ts @@ -70,7 +70,5 @@ export async function tryLogin() { loginStore.setLoginStatus(LOGIN_STATUS.DONE); } catch (error) { loginStore.setLoginStatus(LOGIN_STATUS.FAILED); - - } } diff --git a/src/stores/common.ts b/src/stores/common.ts index 3d8194f90c554719dc84b105195e02572fcc3daf..c5abe61d3d17c8574cc19c30cdbb7e9678202e65 100644 --- a/src/stores/common.ts +++ b/src/stores/common.ts @@ -1,3 +1,4 @@ +import { getCustomCookie } from '@/utils/cookie'; import { defineStore } from 'pinia'; // 语言 @@ -36,3 +37,38 @@ export const useAppearance = defineStore('appearance', { theme: 'light', }), }); + +export const COOKIE_AGREED_STATUS = { + NOT_SIGNED: '0', // 未签署 + ALL_AGREED: '1', // 同意所有cookie + NECCESSARY_AGREED: '2', // 仅同意必要cookie +}; + +export const COOKIE_KEY = 'agreed-cookiepolicy'; + +export const useCookieStore = defineStore('cookie', { + state: () => ({ + status: COOKIE_AGREED_STATUS.NOT_SIGNED, + version: '20240830', + }), + getters: { + isAllAgreed: (state) => state.status === COOKIE_AGREED_STATUS.ALL_AGREED, + }, + actions: { + getUserCookieStatus() { + const cookieVal = getCustomCookie(COOKIE_KEY) ?? '0'; + const cookieStatusVal = cookieVal[0]; + const privacyVersionVal = cookieVal.slice(1); + if (privacyVersionVal !== this.version) { + this.status = COOKIE_AGREED_STATUS.NOT_SIGNED; + } else if (cookieStatusVal === COOKIE_AGREED_STATUS.ALL_AGREED) { + this.status = COOKIE_AGREED_STATUS.ALL_AGREED; + } else if (cookieStatusVal === COOKIE_AGREED_STATUS.NECCESSARY_AGREED) { + this.status = COOKIE_AGREED_STATUS.NECCESSARY_AGREED; + } else { + this.status = COOKIE_AGREED_STATUS.NOT_SIGNED; + } + return this.status; + }, + }, +}); diff --git a/src/stores/user.ts b/src/stores/user.ts index 499d3c74ab5dae49434d3d46d68b804005d1abc7..666007d8a2cb3a9698f0360a3204a041e712bb08 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -16,7 +16,7 @@ export const useUserInfoStore = defineStore('userInfo', { platformAdminPermission: null as boolean | null, // 协作平台maintainer权限 platformMaintainerPermission: null as boolean | null, - } + }; }, getters: { // 获取giteeID @@ -24,7 +24,7 @@ export const useUserInfoStore = defineStore('userInfo', { const id = status.identities.find((id) => id.identity === 'gitee'); return id ? id.login_name : ''; }, - } + }, }); /** @@ -34,6 +34,7 @@ export const useLoginStore = defineStore('login', { state: () => { return { loginStatus: LOGIN_STATUS.NOT, + loginStatusChecked: false, }; }, actions: { @@ -59,4 +60,4 @@ export const useLoginStore = defineStore('login', { return this.loginStatus === LOGIN_STATUS.DONE; }, }, -}); \ No newline at end of file +});