浏览代码

feat(theme): 支持动态设置第三方主题色

- 新增接口 sys.appAccess 用于获取第三方主题色信息
- 在 sys store 中添加获取第三方主题色方法 getThirdPartyThemeColor
- 在 App.vue 中监听主题色变化,动态设置 CSS 变量 --them-color
- 使用 hexToRgba 工具函数转换主题色,实现颜色透明度控制
- 优化各页面颜色样式为动态主题色,替换硬编码颜色值
- 新增 hexToRgba 函数支持十六进制颜色转 rgba 格式
- 调整配置文件,切换开发环境服务器地址
- 重构 Zcontact 组件,调整客服按钮样式与结构
zhangtao 4 天之前
父节点
当前提交
032f007644

+ 14 - 1
src/App.vue

@@ -3,7 +3,21 @@ import useUpdateManager from './composables/useUpdateManager'
 
 useUpdateManager()
 useSysStore().getSystemData()
+useSysStore().getThirdPartyThemeColor()
 onLaunch(() => { })
+
+// 动态设置主题色 CSS 变量
+const manualThemeStore = useManualThemeStore()
+watch(
+  () => manualThemeStore.themeVars.colorTheme,
+  (color) => {
+    console.log('三方主题色==================', color)
+    // #ifdef H5
+    document.documentElement.style.setProperty('--them-color', color || '#9ED605')
+    // #endif
+  },
+  { immediate: true },
+)
 </script>
 
 <style lang="scss">
@@ -11,7 +25,6 @@ onLaunch(() => { })
   min-height: calc(100vh - var(--window-top));
   box-sizing: border-box;
   background: #f6f6f6;
-  --them-color: #9ED605;
 
   .upload {
     .wd-upload__preview {

+ 1 - 0
src/api/apiDefinitions.ts

@@ -36,6 +36,7 @@ export default {
   'sys.updateUserInfo': ['PUT', '/smqjh-system/app-api/v1/members/{memberId}'],
   'sys.selectZhUser': ['GET', '/smqjh-system/app-api/v1/claim/select'],
   'sys.zhUserReceived': ['POST', '/smqjh-system/app-api/v1/claim/received'],
+  'sys.appAccess': ['GET', '/smqjh-system/app-api/v1/appAccess/{accessId}'],
 
   'xsb.categories':['GET', '/smqjh-pms/app-api/v1/categories'],
   'xsb.getCategoryProductList':['POST', '/smqjh-pms/app-api/v1/spu/getCategoryProductList'],

+ 2 - 4
src/api/core/instance.ts

@@ -2,7 +2,7 @@ import AdapterUniapp from '@alova/adapter-uniapp'
 import { createAlova } from 'alova'
 import vueHook from 'alova/vue'
 import { handleAlovaError, handleAlovaResponse } from './handlers'
-import { BASE_URL, accessId } from '@/config'
+import { BASE_URL } from '@/config'
 
 export const alovaInstance = createAlova({
   baseURL: BASE_URL,
@@ -15,9 +15,7 @@ export const alovaInstance = createAlova({
     }
     const { token } = useUserStore()
     method.config.headers.Authorization = token || 'Basic c21xamgtYXBwbGV0OjEyMzQ1Ng=='
-    if (accessId) {
-      method.config.headers.accessId = accessId
-    }
+
     // Add timestamp to prevent caching for GET requests
     if (method.type === 'GET' && CommonUtil.isObj(method.config.params)) {
       method.config.params._t = Date.now()

+ 7 - 0
src/api/globals.d.ts

@@ -116,6 +116,13 @@ interface apiResData<T> {
 declare global {
   interface Apis {
     sys: {
+      appAccess<
+        Config extends Alova2MethodConfig<apiResData<appAccess>> & {
+          pathParams: { accessId: string };
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<appAccess>, 'sys.appAccess', Config>;
       auth<
         Config extends Alova2MethodConfig<apiResData<logoinToken>> & {
           params: loginModel;

+ 2 - 0
src/auto-imports.d.ts

@@ -54,6 +54,7 @@ declare global {
   const getCurrentPath: typeof import('./utils/index')['getCurrentPath']
   const getCurrentScope: typeof import('vue')['getCurrentScope']
   const h: typeof import('vue')['h']
+  const hexToRgba: typeof import('./utils/index')['hexToRgba']
   const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
   const inject: typeof import('vue')['inject']
   const injectLocal: typeof import('@vueuse/core')['injectLocal']
@@ -421,6 +422,7 @@ declare module 'vue' {
     readonly getCurrentPath: UnwrapRef<typeof import('./utils/index')['getCurrentPath']>
     readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
     readonly h: UnwrapRef<typeof import('vue')['h']>
+    readonly hexToRgba: UnwrapRef<typeof import('./utils/index')['hexToRgba']>
     readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
     readonly inject: UnwrapRef<typeof import('vue')['inject']>
     readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>

+ 1 - 7
src/components/Zcontact.vue

@@ -3,16 +3,9 @@
 </script>
 
 <template>
-  <!-- #ifdef MP-WEIXIN -->
   <button open-type="contact" class="zbutton">
     <slot />
   </button>
-  <!-- #endif -->
-  <!-- #ifndef MP-WEIXIN -->
-  <button class="zbutton">
-    <slot />
-  </button>
-  <!-- #endif -->
 </template>
 
 <style scoped lang="scss">
@@ -20,6 +13,7 @@
   all: unset;
   &::after{
     border: none;
+    width: 0 !important;
   }
 }
 </style>

+ 4 - 19
src/config/index.ts

@@ -4,14 +4,14 @@ const mapEnvVersion = {
    */
   // develop: 'http://192.168.1.166:8080', // 张
   // develop: 'http://192.168.1.101:8080',
-  develop: 'http://192.168.0.157:8080',
+  // develop: 'http://192.168.0.157:8080',
   // develop: 'http://192.168.1.253:8080',
   // develop: 'http://192.168.0.19:8080', // 邓
   // develop: 'http://192.168.0.217:8080', // 黄
   // develop: 'http://192.168.0.11:8080', // 王
   // develop: 'http://192.168.1.21:8080', // 田
   // develop: 'http://74949mkfh190.vicp.fun', // 付
-  // develop: 'http://47.109.84.152:8081',
+  develop: 'http://47.109.84.152:8081',
   // develop: 'https://5ed0f7cc.r9.vip.cpolar.cn',
   // develop: 'https://smqjh.api.zswlgz.com',
   /**
@@ -44,8 +44,9 @@ function handleEnvVersion() {
   // #ifdef H5
   const mode = import.meta.env.MODE
   const h5Server = {
-    development: 'http://47.109.84.152:8081',
+    // development: 'http://47.109.84.152:8081',
     // development: 'http://192.168.0.157:8080',
+    development: 'http://192.168.1.21:8080',
     production: 'https://smqjh.api.zswlgz.com',
   }
   return h5Server[mode as 'development' | 'production']
@@ -59,19 +60,3 @@ function handleEnvVersion() {
  * h5小橘重定向地址
  */
 export const REDIRECT_URL = 'https://smqjh.h5.zswlgz.com/#/subPack-refueling/commonTab/index'
-
-/**
- * H5 环境从地址栏获取 accessId 参数
- */
-export const accessId = getAccessId()
-
-function getAccessId(): string {
-  // #ifdef H5
-  const search = window.location.search
-  const params = new URLSearchParams(search)
-  return params.get('accessId') || ''
-  // #endif
-  // #ifndef H5
-  return ''
-  // #endif
-}

+ 1 - 1
src/pages/index/index.vue

@@ -145,7 +145,7 @@ function handleJyBanner() {
 <template>
   <view class="page-class box-border">
     <wd-navbar
-      title="" :custom-style="`background-color: rgba(158,214,5,${opcity});`" :bordered="false" :z-index="99"
+      title="" :custom-style="`background-color: ${hexToRgba(useManualThemeStore().themeVars.colorTheme as string, opcity)};`" :bordered="false" :z-index="99"
       safe-area-inset-top fixed
     >
       <template #left>

+ 7 - 0
src/store/sys.ts

@@ -98,6 +98,13 @@ export const useSysStore = defineStore('system', {
         })
       })
     },
+    /**
+     * 获取第三方主题色
+     */
+    async getThirdPartyThemeColor() {
+      const res = await Apis.sys.appAccess({ pathParams: { accessId: 'ch_001' } })
+      useManualThemeStore().setCurrentThemeColor({ name: '三方主题色', value: 'blue', primary: res.data.themeMainColor })
+    },
 
   },
 })

+ 9 - 16
src/subPack-attractions/attractionsDetail/attractionsDetail.vue

@@ -233,13 +233,14 @@ async function handleMonthChange(e: { year: number, month: number }) {
       class="fixed bottom-0 left-0 z-100 box-border w-full flex items-center justify-between border-t border-#eee bg-white px24rpx py16rpx"
       :style="{ paddingBottom: `${(Number(statusBarHeight) || 44) - 20}px` }"
     >
-      <view class="flex items-center border-t-#EEEEEE">
-        <view class="relative mr-40rpx flex flex-col items-center">
-          <button open-type="contact" class="zbutton" />
-          <wd-icon name="service" size="24px" color="#666" />
-          <text class="text-22rpx text-#666">
-            客服
-          </text>
+      <view class="flex flex-1 items-center justify-between border-t-#EEEEEE pr20rpx">
+        <view class="zbutton">
+          <Zcontact>
+            <wd-icon name="service" size="24px" color="#666" />
+            <view class="text-20rpx text-#666">
+              客服
+            </view>
+          </Zcontact>
         </view>
         <view class="flex flex-col items-center" @click="handleOrder">
           <wd-icon name="list" size="22px" color="#666" />
@@ -259,13 +260,5 @@ async function handleMonthChange(e: { year: number, month: number }) {
 </template>
 
 <style lang="scss" scoped>
-.zbutton{
-  all: unset;
-  width: 70rpx;
-  height: 70rpx;
-  position: absolute;
-  &::after{
-    border: none;
-  }
-}
+
 </style>

+ 1 - 1
src/subPack-xsb/commonTab/components/index.vue

@@ -90,7 +90,7 @@ function handleChangeSwiper(e: UniHelper.SwiperOnChangeEvent) {
   <view class="page-xsb">
     <wd-navbar
       title=""
-      :custom-style="`background-color: rgba(158,214,5,${opcity});`"
+      :custom-style="`background-color: ${hexToRgba(useManualThemeStore().themeVars.colorTheme as string, opcity)};`"
       :bordered="false" :z-index="99" safe-area-inset-top left-arrow fixed @click-left="router.back()"
     >
       <template #left>

+ 31 - 0
src/utils/index.ts

@@ -257,3 +257,34 @@ export function getCityName(address: string): string {
   const municipality = address.match(/(北京|上海|重庆|天津)/)
   return municipality ? municipality[1] : ''
 }
+/**
+ * 将十六进制颜色字符串转换为 rgba 格式
+ * @param hex 十六进制颜色字符串,支持格式如 "#1677FF"、"#1677ff" 或 "#fff"
+ * @param alpha 透明度,取值范围 0-1,默认为 1(完全不透明)
+ * @returns rgba 格式的字符串,例如 "rgba(22, 119, 255, 1)"
+ * @throws 当传入无效的十六进制字符串时抛出错误
+ */
+export function hexToRgba(hex: string, alpha: number = 1): string {
+  // 去除开头的 '#' 符号
+  let raw = hex.replace(/^#/, '')
+
+  // 处理简写形式(例如 "#FFF" 转为 "#FFFFFF")
+  if (raw.length === 3) {
+    raw = raw.split('').map(c => c + c).join('')
+  }
+
+  // 校验长度是否为 6 位
+  if (raw.length !== 6) {
+    throw new Error('Invalid hex color format. Expected 3 or 6 hex digits.')
+  }
+
+  // 解析 R, G, B 分量
+  const r = Number.parseInt(raw.substring(0, 2), 16)
+  const g = Number.parseInt(raw.substring(2, 4), 16)
+  const b = Number.parseInt(raw.substring(4, 6), 16)
+
+  // 确保透明度在有效范围内
+  const validAlpha = Math.min(1, Math.max(0, alpha))
+
+  return `rgba(${r}, ${g}, ${b}, ${validAlpha})`
+}