language.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. <template>
  2. <view class="acct-lang-page">
  3. <view class="acct-lang-nav" :style="{ paddingTop: statusBar + 'px' }">
  4. <view class="acct-lang-nav-row">
  5. <view class="acct-lang-back" @tap="goBack">
  6. <text class="acct-lang-back-icon">←</text>
  7. </view>
  8. <text class="acct-lang-nav-title">{{ copy.title }}</text>
  9. <view class="acct-lang-nav-placeholder" />
  10. </view>
  11. </view>
  12. <scroll-view
  13. scroll-y
  14. class="acct-lang-scroll"
  15. :style="{ height: scrollHeight + 'px' }"
  16. >
  17. <view class="acct-lang-body" :style="{ paddingBottom: safeBottom + 'px' }">
  18. <view class="acct-lang-info-card">
  19. <view class="acct-lang-info-row">
  20. <text class="acct-lang-info-label">{{ copy.systemLanguage }}</text>
  21. <text class="acct-lang-info-value">{{ systemLocale }}</text>
  22. </view>
  23. <view class="acct-lang-info-row acct-lang-info-row--last">
  24. <text class="acct-lang-info-label">{{ copy.applicationLanguage }}</text>
  25. <text class="acct-lang-info-value">{{ currentLabel }}</text>
  26. </view>
  27. </view>
  28. <text class="acct-lang-section-title">{{ copy.pickLanguage }}</text>
  29. <view class="acct-lang-card">
  30. <view
  31. v-for="(item, index) in localeOptions"
  32. :key="item.code"
  33. class="acct-lang-row"
  34. :class="{ 'acct-lang-row--last': index === localeOptions.length - 1 }"
  35. hover-class="acct-lang-row--press"
  36. @tap="onPickLocale(item)"
  37. >
  38. <text class="acct-lang-row-label">{{ item.text }}</text>
  39. <view
  40. class="acct-lang-radio"
  41. :class="{ 'acct-lang-radio--on': item.code === selectedCode }"
  42. >
  43. <view v-if="item.code === selectedCode" class="acct-lang-radio-dot" />
  44. </view>
  45. </view>
  46. </view>
  47. <text class="acct-lang-hint">{{ copy.hint }}</text>
  48. </view>
  49. </scroll-view>
  50. </view>
  51. </template>
  52. <script>
  53. import {
  54. buildLocaleOptions,
  55. getStoredLocaleCode,
  56. localeLabelForCode,
  57. applyLocaleChange,
  58. isAndroidPlatform
  59. } from '@/features/account/locale-settings'
  60. export default {
  61. data() {
  62. return {
  63. statusBar: 0,
  64. scrollHeight: 0,
  65. safeBottom: 0,
  66. systemLocale: '',
  67. selectedCode: 'auto',
  68. isAndroid: false
  69. }
  70. },
  71. computed: {
  72. copy() {
  73. return {
  74. title: this.$t('user.panelLanguage'),
  75. systemLanguage: this.$t('index.system-language'),
  76. applicationLanguage: this.$t('index.application-language'),
  77. pickLanguage: this.$t('index.language'),
  78. hint: this.$t('user.languageHint'),
  79. confirm: this.$t('index.language-change-confirm')
  80. }
  81. },
  82. localeOptions() {
  83. return buildLocaleOptions((key) => this.$t(key))
  84. },
  85. currentLabel() {
  86. return localeLabelForCode(this.localeOptions, this.selectedCode)
  87. }
  88. },
  89. onLoad() {
  90. const sys = uni.getSystemInfoSync()
  91. this.statusBar = sys.statusBarHeight || 0
  92. this.safeBottom = (sys.safeAreaInsets && sys.safeAreaInsets.bottom) || 0
  93. const navH = 44
  94. this.scrollHeight = sys.windowHeight - this.statusBar - navH
  95. this.systemLocale = sys.language || ''
  96. this.selectedCode = getStoredLocaleCode()
  97. this.isAndroid = isAndroidPlatform()
  98. uni.onLocaleChange((e) => {
  99. if (this.selectedCode === 'auto') {
  100. this.systemLocale = e.locale
  101. }
  102. })
  103. },
  104. methods: {
  105. goBack() {
  106. uni.navigateBack({
  107. fail: () => {
  108. uni.switchTab({ url: '/pages/mine/index' })
  109. }
  110. })
  111. },
  112. onPickLocale(item) {
  113. if (!item || item.code === this.selectedCode) return
  114. const run = () => {
  115. applyLocaleChange(item.code, this.$i18n)
  116. this.selectedCode = item.code
  117. this.$store.dispatch('shell/fetchAppConfig')
  118. }
  119. if (this.isAndroid) {
  120. uni.showModal({
  121. content: this.copy.confirm,
  122. success: (res) => {
  123. if (res.confirm) run()
  124. }
  125. })
  126. return
  127. }
  128. run()
  129. }
  130. }
  131. }
  132. </script>
  133. <style scoped>
  134. .acct-lang-page {
  135. min-height: 100vh;
  136. background-color: #f4f6fb;
  137. }
  138. .acct-lang-nav {
  139. background-color: #ffffff;
  140. border-bottom: 1rpx solid #eef1f6;
  141. }
  142. .acct-lang-nav-row {
  143. display: flex;
  144. flex-direction: row;
  145. align-items: center;
  146. height: 88rpx;
  147. padding: 0 24rpx;
  148. box-sizing: border-box;
  149. }
  150. .acct-lang-back {
  151. width: 64rpx;
  152. height: 64rpx;
  153. display: flex;
  154. align-items: center;
  155. justify-content: center;
  156. }
  157. .acct-lang-back-icon {
  158. font-size: 40rpx;
  159. color: #1a1d26;
  160. line-height: 1;
  161. }
  162. .acct-lang-nav-title {
  163. flex: 1;
  164. text-align: center;
  165. font-size: 32rpx;
  166. font-weight: 600;
  167. color: #1a1d26;
  168. }
  169. .acct-lang-nav-placeholder {
  170. width: 64rpx;
  171. }
  172. .acct-lang-body {
  173. padding: 24rpx 28rpx 0;
  174. box-sizing: border-box;
  175. }
  176. .acct-lang-info-card {
  177. background-color: #ffffff;
  178. border-radius: 24rpx;
  179. box-shadow: 0 6rpx 24rpx rgba(74, 140, 255, 0.06);
  180. overflow: hidden;
  181. margin-bottom: 28rpx;
  182. }
  183. .acct-lang-info-row {
  184. display: flex;
  185. flex-direction: row;
  186. align-items: center;
  187. justify-content: space-between;
  188. min-height: 96rpx;
  189. padding: 0 28rpx;
  190. border-bottom: 1rpx solid #f0f2f7;
  191. box-sizing: border-box;
  192. }
  193. .acct-lang-info-row--last {
  194. border-bottom-width: 0;
  195. }
  196. .acct-lang-info-label {
  197. font-size: 28rpx;
  198. color: #8a8d9e;
  199. }
  200. .acct-lang-info-value {
  201. font-size: 28rpx;
  202. color: #1a1d26;
  203. font-weight: 500;
  204. max-width: 360rpx;
  205. text-align: right;
  206. }
  207. .acct-lang-section-title {
  208. display: block;
  209. padding: 0 8rpx 16rpx;
  210. font-size: 26rpx;
  211. color: #8a8d9e;
  212. }
  213. .acct-lang-card {
  214. background-color: #ffffff;
  215. border-radius: 24rpx;
  216. box-shadow: 0 6rpx 24rpx rgba(74, 140, 255, 0.06);
  217. overflow: hidden;
  218. margin-bottom: 20rpx;
  219. }
  220. .acct-lang-row {
  221. display: flex;
  222. flex-direction: row;
  223. align-items: center;
  224. justify-content: space-between;
  225. min-height: 104rpx;
  226. padding: 0 28rpx;
  227. border-bottom: 1rpx solid #f0f2f7;
  228. box-sizing: border-box;
  229. }
  230. .acct-lang-row--last {
  231. border-bottom-width: 0;
  232. }
  233. .acct-lang-row--press {
  234. background-color: #f7f9fc;
  235. }
  236. .acct-lang-row-label {
  237. font-size: 30rpx;
  238. color: #1a1d26;
  239. }
  240. .acct-lang-radio {
  241. width: 40rpx;
  242. height: 40rpx;
  243. border-radius: 20rpx;
  244. border: 2rpx solid #d5dae6;
  245. display: flex;
  246. align-items: center;
  247. justify-content: center;
  248. box-sizing: border-box;
  249. }
  250. .acct-lang-radio--on {
  251. border-color: #4a8cff;
  252. background-color: #eef4ff;
  253. }
  254. .acct-lang-radio-dot {
  255. width: 20rpx;
  256. height: 20rpx;
  257. border-radius: 10rpx;
  258. background: linear-gradient(135deg, #4a8cff 0%, #6b5ce7 100%);
  259. }
  260. .acct-lang-hint {
  261. display: block;
  262. padding: 0 8rpx;
  263. font-size: 24rpx;
  264. line-height: 1.5;
  265. color: #8a8d9e;
  266. }
  267. </style>