SimpleHeader.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <template>
  2. <!-- <div class="block">
  3. <header class="simple-header van-hairline--bottom" :style="{ position: isFixed ? 'fixed' : 'relative' }">
  4. <i v-if="!isback" class="nbicon nbfanhui" @click="goBack"></i>
  5. <i v-else></i>
  6. <div class="simple-header-name">{{ name }}</div>
  7. <i class="nbicon nbmore moreIcon"></i>
  8. </header>
  9. </div> -->
  10. <van-nav-bar v-if="!isback" :title="name" :border="isBorder" left-arrow @click-left="onClickLeft" :style="barStyle">
  11. <!-- 右侧语言切换 -->
  12. <template #right>
  13. <div class="nav-language">
  14. <van-button round size="small" class="lang-trigger" @click="showPopover = true">
  15. <div class="current-lang">
  16. <img :src="currentLang.flag" class="current-flag" />
  17. <span class="lang-code">{{ currentLang.code.toUpperCase() }}</span>
  18. </div>
  19. </van-button>
  20. <van-popover v-model:show="showPopover" :offset="[0, 8]" placement="bottom-end" class="lang-popover">
  21. <van-cell-group>
  22. <van-cell v-for="lang in languages" :key="lang.code"
  23. :class="['lang-item', { active: lang.code === currentLang.code }]" @click="handleChangeLanguage(lang)">
  24. <template #icon>
  25. <img :src="lang.flag" class="flag" :alt="lang.code">
  26. </template>
  27. <div class="lang-content">
  28. <span class="lang-label">{{ lang.label }}</span>
  29. <span class="lang-code">{{ lang.code.toUpperCase() }}</span>
  30. </div>
  31. </van-cell>
  32. </van-cell-group>
  33. </van-popover>
  34. </div>
  35. </template>
  36. </van-nav-bar>
  37. <van-nav-bar v-else :title="name">
  38. <!-- 右侧语言切换 -->
  39. <template #right>
  40. <div class="nav-language">
  41. <van-button round size="small" class="lang-trigger" @click="showPopover = true">
  42. <div class="current-lang">
  43. <img :src="currentLang.flag" class="current-flag" />
  44. <span class="lang-code">{{ currentLang.code.toUpperCase() }}</span>
  45. </div>
  46. </van-button>
  47. <van-popover v-model:show="showPopover" :offset="[0, 8]" placement="bottom-end" class="lang-popover">
  48. <van-cell-group>
  49. <van-cell v-for="lang in languages" :key="lang.code"
  50. :class="['lang-item', { active: lang.code === currentLang.code }]" @click="handleChangeLanguage(lang)">
  51. <template #icon>
  52. <img :src="lang.flag" class="flag" :alt="lang.code">
  53. </template>
  54. <div class="lang-content">
  55. <span class="lang-label">{{ lang.label }}</span>
  56. <span class="lang-code">{{ lang.code.toUpperCase() }}</span>
  57. </div>
  58. </van-cell>
  59. </van-cell-group>
  60. </van-popover>
  61. </div>
  62. </template>
  63. </van-nav-bar>
  64. </template>
  65. <script>
  66. import { ref } from 'vue'
  67. import { useRouter } from 'vue-router'
  68. import { useI18n } from 'vue-i18n';
  69. import { Locale } from 'vant';
  70. // 引入英文语言包
  71. import enUS from "vant/es/locale/lang/en-US";
  72. // 引入简体中文语言包
  73. import zhCN from "vant/es/locale/lang/zh-CN";
  74. import jaJP from "vant/es/locale/lang/ja-JP";
  75. // 引入俄语语言包
  76. import ruRU from "vant/es/locale/lang/ru-RU";
  77. // 引入法语语言包
  78. import frFR from "vant/es/locale/lang/fr-FR";
  79. // 引入西班牙语语言包
  80. import esES from "vant/es/locale/lang/es-ES";
  81. // 引入葡萄牙语语言包
  82. import ptBR from "vant/es/locale/lang/pt-BR";
  83. // 引入乌克兰语语言包
  84. import ukUA from "vant/es/locale/lang/uk-UA";
  85. export default {
  86. props: {
  87. name: {
  88. type: String,
  89. default: ''
  90. },
  91. back: {
  92. type: String,
  93. default: ''
  94. },
  95. noback: {
  96. type: Boolean,
  97. default: false
  98. },
  99. // 是否固定在顶部
  100. isFixed: {
  101. type: Boolean,
  102. default: true
  103. },
  104. // 锁定要跳转的页面路径
  105. targetPath: {
  106. type: String,
  107. default: ''
  108. },
  109. // 是否显示下边框
  110. isBorder: {
  111. type: Boolean,
  112. default: true
  113. },
  114. barStyle: {
  115. type: Object,
  116. default: () => ({})
  117. }
  118. },
  119. emits: ['callback'],
  120. setup(props, ctx) {
  121. const isback = ref(props.noback)
  122. const router = useRouter()
  123. const goBack = () => {
  124. if (props.targetPath) {
  125. router.replace({ path: props.targetPath })
  126. } else if (!props.back) {
  127. router.go(-1)
  128. } else {
  129. router.push({ path: props.back })
  130. }
  131. ctx.emit('callback')
  132. }
  133. const { locale } = useI18n();
  134. // 可用语言列表
  135. const languages = ref([
  136. { code: 'zh', label: '简体中文', vant: zhCN, flag: 'https://flagcdn.com/cn.svg' },
  137. { code: 'en', label: 'English', vant: enUS, flag: 'https://flagcdn.com/us.svg' },
  138. { code: 'ja', label: '日本語', vant: jaJP, flag: 'https://flagcdn.com/jp.svg' },
  139. { code: 'ru', label: 'Русский', vant: ruRU, flag: 'https://flagcdn.com/ru.svg' },
  140. { code: 'fr', label: 'Français', vant: frFR, flag: 'https://flagcdn.com/fr.svg' },
  141. { code: 'es', label: 'Español', vant: esES, flag: 'https://flagcdn.com/es.svg' },
  142. { code: 'pt', label: 'Português', vant: ptBR, flag: 'https://flagcdn.com/pt.svg' },
  143. { code: 'uk', label: 'Українська', vant: ukUA, flag: 'https://flagcdn.com/ua.svg' }
  144. ]);
  145. // 当前语言
  146. const currentLang = ref(languages.value.find(l => l.code === locale.value) || languages.value[0]);
  147. const showPopover = ref(false);
  148. // 切换语言
  149. const handleChangeLanguage = async (lang) => {
  150. currentLang.value = lang;
  151. locale.value = lang.code;
  152. // 1. 切换应用语言
  153. locale.value = lang.code
  154. // 2. 切换 Vant 语言
  155. Locale.use(lang.code, lang.vant)
  156. showPopover.value = false;
  157. // 3. (可选) 持久化存储
  158. localStorage.setItem('curLang', lang.code)
  159. };
  160. const onClickLeft = () => history.back();
  161. return {
  162. goBack,
  163. isback,
  164. onClickLeft,
  165. handleChangeLanguage,
  166. showPopover,
  167. languages,
  168. currentLang
  169. }
  170. }
  171. }
  172. </script>
  173. <style lang="less" scoped>
  174. @import '../common/style/mixin';
  175. .simple-header {
  176. // position: fixed;
  177. top: 0;
  178. left: 0;
  179. z-index: 999;
  180. .fj();
  181. .wh(100%, 44px);
  182. line-height: 44px;
  183. padding: 0 10px;
  184. .boxSizing();
  185. color: #404d74;
  186. background: #fff;
  187. .simple-header-name {
  188. white-space: nowrap;
  189. font-size: 16px;
  190. font-weight: 600;
  191. }
  192. }
  193. .block {
  194. width: 100%;
  195. height: 44px;
  196. }
  197. .moreIcon {
  198. visibility: hidden;
  199. }
  200. .current-lang {
  201. display: flex;
  202. align-items: center;
  203. gap: 6px;
  204. padding: 2px;
  205. }
  206. .current-flag {
  207. width: 20px;
  208. height: 15px;
  209. border-radius: 2px;
  210. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  211. }
  212. .lang-code {
  213. font-size: 14px;
  214. font-weight: 500;
  215. color: #333;
  216. margin-top: 1px;
  217. }
  218. .nav-language {
  219. margin-right: -8px;
  220. }
  221. .lang-trigger {
  222. padding: 4px 8px;
  223. background: rgba(255, 255, 255, 0.9);
  224. border: 1px solid #eee;
  225. transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  226. border: 1px solid #eee;
  227. /* 边框颜色 */
  228. color: #2c87c8;
  229. /* 文字颜色 */
  230. &:hover {
  231. border-color: #2c87c8;
  232. color: #2c87c8;
  233. background: rgba(77, 106, 221, 0.05);
  234. }
  235. }
  236. /* 点击状态 */
  237. .lang-trigger:active {
  238. border-color: #2c87c8;
  239. color: #2c87c8;
  240. background: rgba(77, 106, 221, 0.1);
  241. }
  242. .globe-icon {
  243. font-size: 16px;
  244. color: #666;
  245. margin-right: 6px;
  246. }
  247. .lang-text {
  248. font-size: 14px;
  249. font-weight: 500;
  250. color: #333;
  251. }
  252. .lang-popover {
  253. --van-popover-arrow-size: 8px;
  254. }
  255. .lang-item {
  256. padding: 10px 16px;
  257. &.active {
  258. background: #f5f8ff;
  259. .lang-label {
  260. color: #2c87c8;
  261. }
  262. }
  263. }
  264. /* 调整后的样式 */
  265. .van-cell {
  266. --cell-vertical-padding: 12px;
  267. /* 统一垂直间距 */
  268. display: flex;
  269. align-items: center;
  270. /* 主轴线居中 */
  271. }
  272. .flag {
  273. width: 22px;
  274. height: 16px;
  275. border-radius: 2px;
  276. margin-right: 12px;
  277. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  278. vertical-align: middle;
  279. /* 行内元素垂直对齐 */
  280. position: relative;
  281. top: -1px;
  282. /* 微调视觉平衡 */
  283. }
  284. .lang-content {
  285. display: flex;
  286. align-items: center;
  287. gap: 8px;
  288. }
  289. .lang-label {
  290. line-height: 24px;
  291. font-size: 14px;
  292. color: #333;
  293. gap: 8px;
  294. }
  295. .lang-code {
  296. font-size: 12px;
  297. color: #2c87c8;
  298. font-weight: 600;
  299. /* 增加字重提升可读性 */
  300. letter-spacing: 0.5px;
  301. }
  302. /* 国旗悬停动画 */
  303. .lang-trigger:hover .current-flag {
  304. filter: brightness(0.9);
  305. }
  306. </style>