| | |
| | | export * from './proxy'; |
| | | export * from './proxy' |
| | |
| | | * 2025-09-28 09:31:16 |
| | | */ |
| | | export const createViteProxy = (env) => { |
| | | const { VITE_APP_PROXY, VITE_API_PREFIX, VITE_API_BASE_URL } = env; |
| | | const { VITE_APP_PROXY, VITE_API_PREFIX, VITE_API_BASE_URL } = env |
| | | // 不使用代理直接返回 |
| | | if (!JSON.parse(VITE_APP_PROXY)) return undefined; |
| | | if (!JSON.parse(VITE_APP_PROXY)) return undefined |
| | | const proxy = { |
| | | [VITE_API_PREFIX]: { |
| | | target: VITE_API_BASE_URL, |
| | | changeOrigin: true, |
| | | rewrite: path => path.replace(new RegExp(`^${VITE_API_PREFIX}`), ''), |
| | | }, |
| | | }; |
| | | return proxy; |
| | | }; |
| | | } |
| | | return proxy |
| | | } |
| | |
| | | * @name AutoImportDeps |
| | | * @description 按需加载,自动引入 |
| | | */ |
| | | import AutoImport from 'unplugin-auto-import/vite'; |
| | | import AutoImport from 'unplugin-auto-import/vite' |
| | | |
| | | export const AutoImportDeps = () => { |
| | | return AutoImport({ |
| | |
| | | // 禁用生成 .d.ts 文件,因为项目已转为 JavaScript |
| | | dts: false, |
| | | vueTemplate: true, |
| | | }); |
| | | }; |
| | | }) |
| | | } |
| | |
| | | * @name AutoRegistryComponents |
| | | * @description 按需加载,自动引入 |
| | | */ |
| | | import Components from 'unplugin-vue-components/vite'; |
| | | import Components from 'unplugin-vue-components/vite' |
| | | |
| | | export const AutoRegistryComponents = () => { |
| | | return Components({ |
| | | // 禁用生成 .d.ts 文件,因为项目已转为 JavaScript |
| | | dts: false, |
| | | }); |
| | | }; |
| | | }) |
| | | } |
| | |
| | | import uniPlugin from '@dcloudio/vite-plugin-uni'; |
| | | import ViteRestart from 'vite-plugin-restart'; |
| | | import { AutoImportDeps } from './autoImport'; |
| | | import uniPlugin from '@dcloudio/vite-plugin-uni' |
| | | import ViteRestart from 'vite-plugin-restart' |
| | | import { AutoImportDeps } from './autoImport' |
| | | // import { ConfigImageminPlugin } from './imagemin'; |
| | | // import { ReplaceUrlPlugin } from './replaceUrl'; |
| | | import { AutoRegistryComponents } from './component'; |
| | | import { MCPPlugin } from './mcp'; |
| | | import { ConfigUnoCSSPlugin } from './unocss'; |
| | | import { AutoRegistryComponents } from './component' |
| | | import { MCPPlugin } from './mcp' |
| | | import { ConfigUnoCSSPlugin } from './unocss' |
| | | |
| | | export default function createVitePlugins(isBuild) { |
| | | export default function createVitePlugins (isBuild) { |
| | | const vitePlugins = [ |
| | | // UnoCSS配置 |
| | | ConfigUnoCSSPlugin(), |
| | |
| | | }), |
| | | // 为项目开启 MCP Server |
| | | MCPPlugin(), |
| | | ]; |
| | | ] |
| | | |
| | | if (isBuild) { |
| | | const buildPlugins = [ |
| | |
| | | // CleanImagePlugin(), |
| | | // 打包视图分析 |
| | | // VisualizerPlugin(), |
| | | ]; |
| | | vitePlugins.push(...buildPlugins); |
| | | ] |
| | | vitePlugins.push(...buildPlugins) |
| | | } |
| | | |
| | | return vitePlugins; |
| | | return vitePlugins |
| | | } |
| | |
| | | * @name ReplaceImageUrl |
| | | * @description 替换图片地址 |
| | | */ |
| | | import replaceImageUrl from 'vite-plugin-replace-image-url'; |
| | | import replaceImageUrl from 'vite-plugin-replace-image-url' |
| | | |
| | | export const ReplaceUrlPlugin = () => { |
| | | return replaceImageUrl({ |
| | | publicPath: 'https://photo.example.com/miniprogram', |
| | | sourceDir: 'src/static', |
| | | verbose: true, |
| | | }); |
| | | }; |
| | | }) |
| | | } |
| | |
| | | * @name ConfigUnoCSSPlugin |
| | | * @description UnoCSS相关配置 |
| | | */ |
| | | import UnoCSS from 'unocss/vite'; |
| | | import UnoCSS from 'unocss/vite' |
| | | |
| | | export const ConfigUnoCSSPlugin = () => { |
| | | return UnoCSS(); |
| | | }; |
| | | return UnoCSS() |
| | | } |
| | |
| | | * @name VisualizerPlugin |
| | | * @description 打包视图分析 |
| | | */ |
| | | import { visualizer } from 'rollup-plugin-visualizer'; |
| | | import { visualizer } from 'rollup-plugin-visualizer' |
| | | |
| | | export const VisualizerPlugin = () => { |
| | | return visualizer({ |
| | | emitFile: false, |
| | | filename: 'stats.html', // 分析图生成的文件名 |
| | | open: true, // 如果存在本地服务端口,将在打包后自动展示 |
| | | }); |
| | | }; |
| | | }) |
| | | } |
| | |
| | | defaultIssues: '', |
| | | defaultScope: '', |
| | | defaultSubject: '', |
| | | }; |
| | | } |
| | |
| | | // # 在升级完后,会自动添加很多无用依赖,这需要删除以减小依赖包体积 |
| | | // # 只需要执行下面的命令即可 |
| | | |
| | | import { exec } from 'node:child_process'; |
| | | import { exec } from 'node:child_process' |
| | | |
| | | // 定义要执行的命令 |
| | | const dependencies = [ |
| | |
| | | '@dcloudio/uni-mp-harmony', |
| | | // vue 已经内置了 @vue/runtime-core,这里移除掉 |
| | | '@vue/runtime-core', |
| | | ]; |
| | | ] |
| | | |
| | | // 使用exec执行命令 |
| | | exec(`pnpm remove ${dependencies.join(' ')}`, (error, stdout, stderr) => { |
| | | if (error) { |
| | | // 如果有错误,打印错误信息 |
| | | console.error(`执行出错: ${error}`); |
| | | return; |
| | | console.error(`执行出错: ${error}`) |
| | | return |
| | | } |
| | | // 打印正常输出 |
| | | console.log(`stdout: ${stdout}`); |
| | | console.log(`stdout: ${stdout}`) |
| | | // 如果有错误输出,也打印出来 |
| | | console.error(`stderr: ${stderr}`); |
| | | }); |
| | | console.error(`stderr: ${stderr}`) |
| | | }) |
| | |
| | | * @link https://github.com/toplenboren/simple-git-hooks |
| | | * @see 参考:https://github.com/vuejs/vue-next/blob/master/.github/commit-convention.md |
| | | */ |
| | | import { readFileSync } from 'node:fs'; |
| | | import path from 'node:path'; |
| | | import process from 'node:process'; |
| | | import pico from 'picocolors'; |
| | | import { readFileSync } from 'node:fs' |
| | | import path from 'node:path' |
| | | import process from 'node:process' |
| | | import pico from 'picocolors' |
| | | |
| | | const msgPath = path.resolve('.git/COMMIT_EDITMSG'); |
| | | const msg = readFileSync(msgPath, 'utf-8').trim(); |
| | | const msgPath = path.resolve('.git/COMMIT_EDITMSG') |
| | | const msg = readFileSync(msgPath, 'utf-8').trim() |
| | | |
| | | const commitRE |
| | | = /^(?:revert: )?(?:feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|mod|release|strengthen)(?:\(.+\))?: .{1,50}/; |
| | | = /^(?:revert: )?(?:feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|mod|release|strengthen)(?:\(.+\))?: .{1,50}/ |
| | | |
| | | if (!commitRE.test(msg)) { |
| | | console.log(pico.yellow(`\n提交的信息: ${msg}\n`)); |
| | | console.log(pico.yellow(`\n提交的信息: ${msg}\n`)) |
| | | console.error( |
| | | ` ${pico.white(pico.bgRed(' 格式错误 '))} ${pico.red( |
| | | '无效的提交信息格式.', |
| | | )}\n\n${ |
| | | pico.red(' 正确的提交消息格式. 例如:\n\n') |
| | | )}\n\n${pico.red(' 正确的提交消息格式. 例如:\n\n') |
| | | } ${pico.green('feat: add a new feature')}\n` |
| | | + ` ${pico.green('fix: fixed an bug')}`, |
| | | ); |
| | | process.exit(1); |
| | | ) |
| | | process.exit(1) |
| | | } |
| | |
| | | * 2025-09-28 09:31:16 |
| | | --> |
| | | <script setup> |
| | | import { onHide, onLaunch, onShow } from "@dcloudio/uni-app" |
| | | import { useAppStore } from "@/store" |
| | | import { mpUpdate } from "@/utils/index" |
| | | import { onHide, onLaunch, onShow } from "@dcloudio/uni-app"; |
| | | import { useAppStore } from "@/store"; |
| | | import { mpUpdate } from "@/utils/index"; |
| | | |
| | | const appStore = useAppStore() |
| | | const appStore = useAppStore(); |
| | | |
| | | onLaunch(() => { |
| | | console.log("App Launch") |
| | | console.log("App Launch"); |
| | | |
| | | // 初始化系统信息 |
| | | appStore.initSystemInfo() |
| | | appStore.initSystemInfo(); |
| | | |
| | | // #ifdef MP-WEIXIN |
| | | mpUpdate() |
| | | mpUpdate(); |
| | | // #endif |
| | | }) |
| | | }); |
| | | |
| | | onShow(() => { |
| | | console.log("App Show") |
| | | }) |
| | | console.log("App Show"); |
| | | }); |
| | | |
| | | onHide(() => { |
| | | console.log("App Hide") |
| | | }) |
| | | |
| | | console.log("App Hide"); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | /* 每个页面公共css */ |
| | | @import 'uview-plus/index.scss'; |
| | | @import '@/static/styles/common.scss'; |
| | | @import "uview-plus/index.scss"; |
| | | @import "@/static/styles/common.scss"; |
| | | </style> |
| | |
| | | |
| | | <view class="flex flex-col"> |
| | | <span class="pt-30rpx text-black font-bold">{{ initSubTitle }}</span> |
| | | <span class="pt-30rpx text-sm text-black">1.为向您提供基本的服务,我们会遵循正当、合法、必要的原则收集和使用必要的信息。</span> |
| | | <span class="pt-30rpx text-sm text-black">2.基于您的授权我们可能会收集和使用您的相关信息,您有权拒绝或取消授权。</span> |
| | | <span class="pt-30rpx text-sm text-black">3.未经您的授权同意,我们不会将您的信息共享给第三方或用于您未授权的其他用途。</span> |
| | | <span class="pt-30rpx text-sm text-black">4.详细信息请您完整阅读<text class="text-decoration" @click="openPrivacyContract">{{ |
| | | initPrivacyContractName |
| | | }}</text></span> |
| | | <span class="pt-30rpx text-sm text-black" |
| | | >1.为向您提供基本的服务,我们会遵循正当、合法、必要的原则收集和使用必要的信息。</span |
| | | > |
| | | <span class="pt-30rpx text-sm text-black" |
| | | >2.基于您的授权我们可能会收集和使用您的相关信息,您有权拒绝或取消授权。</span |
| | | > |
| | | <span class="pt-30rpx text-sm text-black" |
| | | >3.未经您的授权同意,我们不会将您的信息共享给第三方或用于您未授权的其他用途。</span |
| | | > |
| | | <span class="pt-30rpx text-sm text-black" |
| | | >4.详细信息请您完整阅读<text |
| | | class="text-decoration" |
| | | @click="openPrivacyContract" |
| | | >{{ initPrivacyContractName }}</text |
| | | ></span |
| | | > |
| | | </view> |
| | | |
| | | <view class="mt-30rpx flex items-center justify-around pt-10rpx"> |
| | | <view class="min-w-100px"> |
| | | <button class="button button-default" @click="disagree"> |
| | | 拒绝 |
| | | </button> |
| | | <button class="button button-default" @click="disagree">拒绝</button> |
| | | </view> |
| | | <view class="min-w-100px"> |
| | | <button |
| | |
| | | <script setup> |
| | | const props = withDefaults(defineProps(), { |
| | | modelValue: false, |
| | | title: '', |
| | | subTitle: '', |
| | | title: "", |
| | | subTitle: "", |
| | | disableCheckPrivacy: true, |
| | | agreePrivacyId: 'agree-btn', |
| | | agreePrivacyId: "agree-btn", |
| | | }); |
| | | |
| | | const emit = defineEmits([ |
| | | 'update:modelValue', |
| | | 'needPrivacyAuthorization', |
| | | 'agree', |
| | | 'disagree', |
| | | "update:modelValue", |
| | | "needPrivacyAuthorization", |
| | | "agree", |
| | | "disagree", |
| | | ]); |
| | | // 初始化的标题 |
| | | const initTitle = ref('隐私政策概要'); |
| | | const initTitle = ref("隐私政策概要"); |
| | | // 初始化的副标题 |
| | | const initSubTitle = ref(''); |
| | | const initSubTitle = ref(""); |
| | | // 隐私政策 |
| | | const initPrivacyContractName = ref('隐私政策'); |
| | | const initPrivacyContractName = ref("隐私政策"); |
| | | |
| | | // 打开隐私 |
| | | function openAgreePrivacy() { |
| | | emit('update:modelValue', true); |
| | | emit("update:modelValue", true); |
| | | } |
| | | |
| | | // 关闭隐私 |
| | | function closeAgreePrivacy() { |
| | | emit('update:modelValue', false); |
| | | emit("update:modelValue", false); |
| | | } |
| | | |
| | | // 需要初始化的数据 |
| | | function initData() { |
| | | initTitle.value = props.title || initTitle.value; |
| | | initSubTitle.value |
| | | = props.subTitle |
| | | || `亲爱的用户,感谢您一直以来的支持!为了更好地保护您的权益,同时遵守相关监管要求,请认真阅读${initPrivacyContractName.value},特向您说明如下:`; |
| | | initSubTitle.value = |
| | | props.subTitle || |
| | | `亲爱的用户,感谢您一直以来的支持!为了更好地保护您的权益,同时遵守相关监管要求,请认真阅读${initPrivacyContractName.value},特向您说明如下:`; |
| | | } |
| | | |
| | | // 检测是否授权 |
| | |
| | | // 需要弹出隐私协议 |
| | | openAgreePrivacy(); |
| | | } |
| | | } |
| | | else { |
| | | } else { |
| | | // 用户已经同意过隐私协议,所以不需要再弹出隐私协议,也能调用已声明过的隐私接口 |
| | | // wx.getUserProfile() |
| | | } |
| | |
| | | |
| | | // 同意 |
| | | function agree(e) { |
| | | const buttonId = e.target.id || 'agree-btn'; |
| | | emit('agree', buttonId); |
| | | emit('update:modelValue', false); |
| | | const buttonId = e.target.id || "agree-btn"; |
| | | emit("agree", buttonId); |
| | | emit("update:modelValue", false); |
| | | } |
| | | |
| | | // 拒绝 |
| | | function disagree() { |
| | | emit('disagree'); |
| | | emit("disagree"); |
| | | closeAgreePrivacy(); |
| | | } |
| | | |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { useI18n } from "vue-i18n" |
| | | import { useI18n } from "vue-i18n"; |
| | | |
| | | const props = defineProps({ |
| | | size: { |
| | | type: Number, |
| | | default: 40, |
| | | required: false |
| | | } |
| | | }) |
| | | const emit = defineEmits(["change"]) |
| | | const { locale, t } = useI18n() |
| | | required: false, |
| | | }, |
| | | }); |
| | | const emit = defineEmits(["change"]); |
| | | const { locale, t } = useI18n(); |
| | | const langStyle = { |
| | | fontSize: `${props.size}rpx` |
| | | } |
| | | fontSize: `${props.size}rpx`, |
| | | }; |
| | | const langOptions = computed(() => { |
| | | return [ |
| | | { label: t("locale.en"), value: "en" }, |
| | | { label: t("locale.zh-hans"), value: "zh-Hans" } |
| | | ] |
| | | }) |
| | | { label: t("locale.zh-hans"), value: "zh-Hans" }, |
| | | ]; |
| | | }); |
| | | const langIndex = computed(() => { |
| | | return langOptions.value.findIndex(item => { |
| | | return item.value === locale.value |
| | | }) |
| | | }) |
| | | return langOptions.value.findIndex((item) => { |
| | | return item.value === locale.value; |
| | | }); |
| | | }); |
| | | |
| | | function handleLangChange(event) { |
| | | const lang = langOptions.value[event.detail.value].value |
| | | locale.value = lang |
| | | uni.setLocale(lang) |
| | | emit("change", lang) |
| | | const lang = langOptions.value[event.detail.value].value; |
| | | locale.value = lang; |
| | | uni.setLocale(lang); |
| | | emit("change", lang); |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped></style> |
| | |
| | | <template> |
| | | <view class="flex flex-wrap gap-3"> |
| | | <view |
| | | v-for="(item, index) in colors" :key="index" |
| | | v-for="(item, index) in colors" |
| | | :key="index" |
| | | class="mb-10rpx h-40rpx w-40rpx center cursor-pointer border-4rpx border-gray-300 rounded-4rpx border-solid" |
| | | :class="{ 'border-white': theme === item.name }" :style="{ backgroundColor: item.color }" |
| | | :class="{ 'border-white': theme === item.name }" |
| | | :style="{ backgroundColor: item.color }" |
| | | @click="changeTheme(item.name)" |
| | | > |
| | | <view v-if="theme === item.name" class="i-mdi-check text-32rpx c-#fff" /> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { useAppStore } from "@/store" |
| | | import { useAppStore } from "@/store"; |
| | | |
| | | const appStore = useAppStore() |
| | | const appStore = useAppStore(); |
| | | |
| | | const colors = [ |
| | | { |
| | | name: "", |
| | | color: "#21d59d" |
| | | color: "#21d59d", |
| | | }, |
| | | { |
| | | name: "blue", |
| | | color: "#3c9cff" |
| | | } |
| | | ] |
| | | color: "#3c9cff", |
| | | }, |
| | | ]; |
| | | |
| | | const theme = computed(() => appStore.getTheme) |
| | | const theme = computed(() => appStore.getTheme); |
| | | |
| | | function changeTheme(theme) { |
| | | appStore.setTheme(theme) |
| | | appStore.setTheme(theme); |
| | | } |
| | | |
| | | </script> |
| | |
| | | * // 获取剪切板 |
| | | * const data = await getClipboardData() |
| | | */ |
| | | export default function useClipboard() { |
| | | export default function useClipboard () { |
| | | const setClipboardData = ({ data, showToast = true }) => { |
| | | return new Promise((resolve, reject) => { |
| | | uni.setClipboardData({ |
| | |
| | | * // 隐藏loading |
| | | * hideLoading() |
| | | */ |
| | | export default function useLoading() { |
| | | export default function useLoading () { |
| | | const showLoading = (content = "加载中") => { |
| | | uni.showLoading({ |
| | | title: content, |
| | |
| | | * - 地址解析 |
| | | * - 距离计算 |
| | | */ |
| | | export default function useLocation() { |
| | | export default function useLocation () { |
| | | // 当前位置信息 |
| | | const location = ref(null) |
| | | |
| | |
| | | const a = |
| | | Math.sin(dLat / 2) * Math.sin(dLat / 2) + |
| | | Math.cos((lat1 * Math.PI) / 180) * |
| | | Math.cos((lat2 * Math.PI) / 180) * |
| | | Math.sin(dLon / 2) * |
| | | Math.sin(dLon / 2) |
| | | Math.cos((lat2 * Math.PI) / 180) * |
| | | Math.sin(dLon / 2) * |
| | | Math.sin(dLon / 2) |
| | | |
| | | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) |
| | | const distance = R * c |
| | |
| | | * const {showModal} = useModal() |
| | | * showModal('提示内容') |
| | | */ |
| | | export default function useModal() { |
| | | export default function useModal () { |
| | | const showModal = (content, options) => { |
| | | return new Promise((resolve, reject) => { |
| | | uni.showModal({ |
| | |
| | | // 1.微信小程序端点击tabbar的底层逻辑不触发uni.switchTab |
| | | // 2.h5在浏览器地址栏输入url后跳转不触发uni的路由api |
| | | // 3.首次启动加载的页面不触发uni的路由api |
| | | export default async function usePermission() { |
| | | export default async function usePermission () { |
| | | return hasPerm(currentRoute()) |
| | | } |
| | |
| | | * onShareAppMessage() |
| | | * onShareTimeline() |
| | | */ |
| | | export default function useShare(options) { |
| | | export default function useShare (options) { |
| | | // #ifdef MP-WEIXIN |
| | | const title = options?.title ?? "" |
| | | const path = options?.path ?? "" |
| | |
| | | /** |
| | | * 主题Hook |
| | | */ |
| | | export default function useTheme() { |
| | | export default function useTheme () { |
| | | const appStore = useAppStore() |
| | | |
| | | // 当前主题模式 |
| | |
| | | } |
| | | }) |
| | | |
| | | function setupI18n(app) { |
| | | function setupI18n (app) { |
| | | app.use(i18n) |
| | | } |
| | | |
| | |
| | | import "virtual:uno.css" |
| | | import 'leaflet/dist/leaflet.css' |
| | | |
| | | export function createApp() { |
| | | export function createApp () { |
| | | const app = createSSRApp(App) |
| | | app.use(setupPlugins) |
| | | |
| | |
| | | <!-- 巡检任务 --> |
| | | <!-- 巡检任务 --> |
| | | <template> |
| | | <z-paging ref="pagingRef" v-model="dataList" @query="queryList"> |
| | | <view v-for="(item, index) in dataList" :key="index"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | const pagingRef = ref(null) |
| | | const pagingRef = ref(null); |
| | | |
| | | const dataList = ref([]) |
| | | const dataList = ref([]); |
| | | |
| | | const urls = [ |
| | | "https://picsum.photos/100/100?random=1", |
| | |
| | | "https://picsum.photos/100/100?random=7", |
| | | "https://picsum.photos/100/100?random=8", |
| | | "https://picsum.photos/100/100?random=9", |
| | | "https://picsum.photos/100/100?random=10" |
| | | ] |
| | | "https://picsum.photos/100/100?random=10", |
| | | ]; |
| | | |
| | | function queryList(pageNo, pageSize) { |
| | | console.log("[ pageNo ] >", pageNo) |
| | | console.log("[ pageSize ] >", pageSize) |
| | | console.log("[ pageNo ] >", pageNo); |
| | | console.log("[ pageSize ] >", pageSize); |
| | | // 这里的pageNo和pageSize会自动计算好,直接传给服务器即可 |
| | | // 这里的请求只是演示,请替换成自己的项目的网络请求,并在网络请求回调中通过pagingRef.value.complete(请求回来的数组)将请求结果传给z-paging |
| | | setTimeout(() => { |
| | | // 1秒之后停止刷新动画 |
| | | const list = [] |
| | | const list = []; |
| | | for (let i = 0; i < 30; i++) |
| | | list.push(urls[uni.$u.random(0, urls.length - 1)]) |
| | | list.push(urls[uni.$u.random(0, urls.length - 1)]); |
| | | |
| | | pagingRef.value?.complete(list) |
| | | }, 1000) |
| | | pagingRef.value?.complete(list); |
| | | }, 1000); |
| | | // this.$request |
| | | // .queryList({ pageNo, pageSize }) |
| | | // .then(res => { |
| | |
| | | // pagingRef.value.complete(false); |
| | | // }); |
| | | } |
| | | |
| | | </script> |
| | |
| | | <template> |
| | | <view> |
| | | <view class="login-form-wrap"> |
| | | <view class="title"> |
| | | 欢迎登录 |
| | | </view> |
| | | <input v-model="tel" class="u-border-bottom" type="number" placeholder="请输入手机号"> |
| | | <view class="title"> 欢迎登录 </view> |
| | | <input |
| | | v-model="tel" |
| | | class="u-border-bottom" |
| | | type="number" |
| | | placeholder="请输入手机号" |
| | | /> |
| | | <view class="u-border-bottom my-40rpx flex"> |
| | | <input v-model="code" class="flex-1" type="number" placeholder="请输入验证码"> |
| | | <input |
| | | v-model="code" |
| | | class="flex-1" |
| | | type="number" |
| | | placeholder="请输入验证码" |
| | | /> |
| | | <view> |
| | | <u-code ref="uCodeRef" @change="codeChange" /> |
| | | <u-button :text="tips" type="success" size="mini" @click="getCode" /> |
| | |
| | | </button> |
| | | |
| | | <view class="alternative"> |
| | | <view class="password"> |
| | | 密码登录 |
| | | </view> |
| | | <view class="password"> 密码登录 </view> |
| | | <view class="issue flex items-center"> |
| | | 遇到问题 <text class="i-mdi-help" /> |
| | | </view> |
| | |
| | | </view> |
| | | <view class="hint"> |
| | | 登录代表同意 |
| | | <text class="link"> |
| | | 用户协议、隐私政策, |
| | | </text> |
| | | <text class="link"> 用户协议、隐私政策, </text> |
| | | 并授权使用您的账号信息(如昵称、头像、收获地址)以便您统一管理 |
| | | </view> |
| | | </view> |
| | |
| | | HOME_PATH, |
| | | isTabBarPath, |
| | | LOGIN_PATH, |
| | | removeQueryString |
| | | } from "@/router" |
| | | import { setToken } from "@/utils/auth" |
| | | removeQueryString, |
| | | } from "@/router"; |
| | | import { setToken } from "@/utils/auth"; |
| | | // import { useUserStore } from '@/store'; |
| | | |
| | | // const userStore = useUserStore(); |
| | | const tel = ref("18502811111") |
| | | const code = ref("1234") |
| | | const tips = ref() |
| | | const uCodeRef = ref(null) |
| | | let redirect = HOME_PATH |
| | | const tel = ref("18502811111"); |
| | | const code = ref("1234"); |
| | | const tips = ref(); |
| | | const uCodeRef = ref(null); |
| | | let redirect = HOME_PATH; |
| | | |
| | | const inputStyle = computed(() => { |
| | | const style = {} |
| | | const style = {}; |
| | | if (tel.value && code.value) { |
| | | style.color = "#fff" |
| | | style.backgroundColor = uni.$u.color.warning |
| | | style.color = "#fff"; |
| | | style.backgroundColor = uni.$u.color.warning; |
| | | } |
| | | return style |
| | | }) |
| | | return style; |
| | | }); |
| | | |
| | | function codeChange(text) { |
| | | tips.value = text |
| | | tips.value = text; |
| | | } |
| | | |
| | | function getCode() { |
| | | if (uCodeRef.value?.canGetCode) { |
| | | // 模拟向后端请求验证码 |
| | | uni.showLoading({ |
| | | title: "正在获取验证码" |
| | | }) |
| | | title: "正在获取验证码", |
| | | }); |
| | | setTimeout(() => { |
| | | uni.hideLoading() |
| | | uni.$u.toast("验证码已发送") |
| | | uni.hideLoading(); |
| | | uni.$u.toast("验证码已发送"); |
| | | // 通知验证码组件内部开始倒计时 |
| | | uCodeRef.value?.start() |
| | | }, 1000) |
| | | uCodeRef.value?.start(); |
| | | }, 1000); |
| | | } else { |
| | | uni.$u.toast("倒计时结束后再发送") |
| | | uni.$u.toast("倒计时结束后再发送"); |
| | | } |
| | | } |
| | | async function submit() { |
| | | if (!uni.$u.test.mobile(Number(tel.value))) { |
| | | uni.$u.toast("请输入正确的手机号") |
| | | return |
| | | uni.$u.toast("请输入正确的手机号"); |
| | | return; |
| | | } |
| | | if (!code.value) { |
| | | uni.$u.toast("请输入验证码") |
| | | return |
| | | uni.$u.toast("请输入验证码"); |
| | | return; |
| | | } |
| | | // 登录请求 |
| | | // const res = await userStore.login({ phone: tel.value, code: code.value }).catch(() => { |
| | | // uni.$u.toast('登录失败'); |
| | | // }); |
| | | // if (!res) return; |
| | | setToken("1234567890") |
| | | setToken("1234567890"); |
| | | setTimeout(() => { |
| | | uni.$u.route({ |
| | | type: isTabBarPath(redirect) ? "switchTab" : "redirectTo", |
| | | url: redirect |
| | | }) |
| | | }, 800) |
| | | url: redirect, |
| | | }); |
| | | }, 800); |
| | | } |
| | | |
| | | onLoad(options => { |
| | | onLoad((options) => { |
| | | if (options.redirect && removeQueryString(options.redirect) !== LOGIN_PATH) { |
| | | redirect = decodeURIComponent(options.redirect) |
| | | redirect = decodeURIComponent(options.redirect); |
| | | } |
| | | }) |
| | | |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | |
| | | <!-- 地图 --> |
| | | <template> |
| | | <!-- 地图容器 --> |
| | | <view id="map" class="map" style="width: 100%; height: 80vh;"></view> |
| | | <view id="map" class="map" style="width: 100%; height: 80vh"></view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | onShow(() => { |
| | | console.log(222) |
| | | // #ifdef APP-PLUS |
| | | onShow(() => { |
| | | console.log(222); |
| | | // #ifdef APP-PLUS |
| | | |
| | | console.log(111) |
| | | plus.screen.lockOrientation('landscape-primary'); |
| | | // #endif |
| | | }) |
| | | console.log(111); |
| | | plus.screen.lockOrientation("landscape-primary"); |
| | | // #endif |
| | | }); |
| | | |
| | | onHide(() => { |
| | | console.log(123123) |
| | | // #ifdef APP-PLUS |
| | | plus.screen.lockOrientation('portrait-primary'); |
| | | // #endif |
| | | }) |
| | | onHide(() => { |
| | | console.log(123123); |
| | | // #ifdef APP-PLUS |
| | | plus.screen.lockOrientation("portrait-primary"); |
| | | // #endif |
| | | }); |
| | | </script> |
| | | |
| | | <script module="leaflet" lang="renderjs"> |
| | | import L from 'leaflet' |
| | | import { |
| | | onBeforeUnmount, |
| | | onMounted |
| | | } from 'vue'; |
| | | import L from 'leaflet' |
| | | import { |
| | | onBeforeUnmount, |
| | | onMounted |
| | | } from 'vue'; |
| | | |
| | | export default { |
| | | mounted() { |
| | | var basemapLayer0 = L.tileLayer( |
| | | 'http://t1.tianditu.com/vec_c/wmts?layer=vec&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', { |
| | | maxZoom: 18, |
| | | minZoom: 1, |
| | | tileSize: 256, |
| | | zoomOffset: 1 |
| | | }); |
| | | var basemapLayer1 = L.tileLayer( |
| | | 'http://t1.tianditu.com/cva_c/wmts?layer=cva&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', { |
| | | maxZoom: 18, |
| | | minZoom: 1, |
| | | tileSize: 256, |
| | | zoomOffset: 1 |
| | | }); |
| | | var basemapLayer2 = L.tileLayer( |
| | | 'http://t1.tianditu.com/img_c/wmts?layer=img&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', { |
| | | maxZoom: 18, |
| | | minZoom: 1, |
| | | tileSize: 256, |
| | | zoomOffset: 1 |
| | | }); |
| | | var basemapLayer3 = L.tileLayer( |
| | | 'http://t1.tianditu.com/cia_c/wmts?layer=cia&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', { |
| | | maxZoom: 18, |
| | | minZoom: 1, |
| | | tileSize: 256, |
| | | zoomOffset: 1 |
| | | }); |
| | | var basemap0 = L.layerGroup([basemapLayer0, basemapLayer1]); |
| | | var basemap1 = L.layerGroup([basemapLayer2, basemapLayer3]); |
| | | export default { |
| | | mounted() { |
| | | var basemapLayer0 = L.tileLayer( |
| | | 'http://t1.tianditu.com/vec_c/wmts?layer=vec&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', { |
| | | maxZoom: 18, |
| | | minZoom: 1, |
| | | tileSize: 256, |
| | | zoomOffset: 1 |
| | | }); |
| | | var basemapLayer1 = L.tileLayer( |
| | | 'http://t1.tianditu.com/cva_c/wmts?layer=cva&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', { |
| | | maxZoom: 18, |
| | | minZoom: 1, |
| | | tileSize: 256, |
| | | zoomOffset: 1 |
| | | }); |
| | | var basemapLayer2 = L.tileLayer( |
| | | 'http://t1.tianditu.com/img_c/wmts?layer=img&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', { |
| | | maxZoom: 18, |
| | | minZoom: 1, |
| | | tileSize: 256, |
| | | zoomOffset: 1 |
| | | }); |
| | | var basemapLayer3 = L.tileLayer( |
| | | 'http://t1.tianditu.com/cia_c/wmts?layer=cia&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=e110584a27d506da2740edca951683f4', { |
| | | maxZoom: 18, |
| | | minZoom: 1, |
| | | tileSize: 256, |
| | | zoomOffset: 1 |
| | | }); |
| | | var basemap0 = L.layerGroup([basemapLayer0, basemapLayer1]); |
| | | var basemap1 = L.layerGroup([basemapLayer2, basemapLayer3]); |
| | | |
| | | let map = L.map('map', { |
| | | preferCanvas: true, |
| | | crs: L.CRS.EPSG4326, |
| | | layers: [basemap1], |
| | | zoomControl: false, |
| | | attributionControl: false, |
| | | doubleClickZoom: false, |
| | | editable: true //绘制控件 |
| | | }).setView([25.992338, 114.823254], 13); |
| | | } |
| | | let map = L.map('map', { |
| | | preferCanvas: true, |
| | | crs: L.CRS.EPSG4326, |
| | | layers: [basemap1], |
| | | zoomControl: false, |
| | | attributionControl: false, |
| | | doubleClickZoom: false, |
| | | editable: true //绘制控件 |
| | | }).setView([25.992338, 114.823254], 13); |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* Leaflet 默认容器要有高度 */ |
| | | .map { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | /* Leaflet 默认容器要有高度 */ |
| | | .map { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | </style> |
| | |
| | | <u-avatar src="/static/images/logo.png" size="70" /> |
| | | </view> |
| | | <view class="flex-1"> |
| | | <view class="pb-20rpx text-36rpx"> |
| | | uni-app |
| | | </view> |
| | | <view class="pb-20rpx text-36rpx"> uni-app </view> |
| | | <view class="u-tips-color text-28rpx" @click="toCopy"> |
| | | 微信号:uni-app |
| | | </view> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { useClipboard, usePermission } from "@/hooks" |
| | | import { useClipboard, usePermission } from "@/hooks"; |
| | | |
| | | const { setClipboardData, getClipboardData } = useClipboard() |
| | | const { setClipboardData, getClipboardData } = useClipboard(); |
| | | |
| | | // 复制 |
| | | const toCopy = async () => { |
| | | await setClipboardData({ data: "1234567890" }) |
| | | const data = await getClipboardData() |
| | | console.log("[ data ] >", data) |
| | | } |
| | | await setClipboardData({ data: "1234567890" }); |
| | | const data = await getClipboardData(); |
| | | console.log("[ data ] >", data); |
| | | }; |
| | | |
| | | // 登录鉴权,微信小程序端点击tabbar的底层逻辑不触发uni.switchTab,需要在页面onShow生命周期中校验权限 |
| | | onShow(async () => { |
| | | const hasPermission = await usePermission() |
| | | console.log(hasPermission ? "已登录" : "未登录,拦截跳转") |
| | | }) |
| | | |
| | | const hasPermission = await usePermission(); |
| | | console.log(hasPermission ? "已登录" : "未登录,拦截跳转"); |
| | | }); |
| | | </script> |
| | |
| | | <!-- 事件工单 --> |
| | | <template> |
| | | <view class="min-h-screen flex flex-col items-center"> |
| | | <image class="mb-50rpx mt-200rpx h-200rpx w-200rpx" src="@/static/images/logo.png" width="200rpx" height="200rpx" /> |
| | | <image |
| | | class="mb-50rpx mt-200rpx h-200rpx w-200rpx" |
| | | src="@/static/images/logo.png" |
| | | width="200rpx" |
| | | height="200rpx" |
| | | /> |
| | | <view class="flex justify-center"> |
| | | <text class="font-size-36rpx"> |
| | | {{ $t('home.intro') }} |
| | | {{ $t("home.intro") }} |
| | | </text> |
| | | </view> |
| | | <view class="mt-100rpx flex gap-30rpx"> |
| | |
| | | |
| | | <!-- #ifdef MP-WEIXIN --> |
| | | <!-- 隐私协议组件 --> |
| | | <agree-privacy v-model="showAgreePrivacy" :disable-check-privacy="false" @agree="handleAgree" /> |
| | | <agree-privacy |
| | | v-model="showAgreePrivacy" |
| | | :disable-check-privacy="false" |
| | | @agree="handleAgree" |
| | | /> |
| | | <!-- #endif --> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | // #ifdef MP-WEIXIN |
| | | import { useShare } from "@/hooks" |
| | | import { useShare } from "@/hooks"; |
| | | // #endif |
| | | |
| | | // #ifdef MP-WEIXIN |
| | |
| | | const { onShareAppMessage, onShareTimeline } = useShare({ |
| | | title: "首页", |
| | | path: "pages/tab/home/index", |
| | | imageUrl: "" |
| | | }) |
| | | onShareAppMessage() |
| | | onShareTimeline() |
| | | imageUrl: "", |
| | | }); |
| | | onShareAppMessage(); |
| | | onShareTimeline(); |
| | | // #endif |
| | | |
| | | const title = ref() |
| | | title.value = import.meta.env.VITE_APP_TITLE |
| | | const title = ref(); |
| | | title.value = import.meta.env.VITE_APP_TITLE; |
| | | |
| | | const showAgreePrivacy = ref(false) |
| | | const showAgreePrivacy = ref(false); |
| | | |
| | | // 同意隐私协议 |
| | | function handleAgree() { |
| | | console.log("同意隐私政策") |
| | | console.log("同意隐私政策"); |
| | | } |
| | | |
| | | // 打开github |
| | | function toGithub() { |
| | | if (window?.open) { |
| | | window.open("https://github.com/oyjt/uniapp-vue3-template") |
| | | window.open("https://github.com/oyjt/uniapp-vue3-template"); |
| | | } else { |
| | | uni.$u.toast("请使用浏览器打开") |
| | | uni.$u.toast("请使用浏览器打开"); |
| | | } |
| | | } |
| | | |
| | | </script> |
| | |
| | | import setupUI from "./ui" |
| | | |
| | | export default { |
| | | install(app) { |
| | | install (app) { |
| | | // UI扩展配置 |
| | | setupUI(app) |
| | | // 状态管理 |
| | |
| | | * @param {string} path |
| | | * @returns {boolean} 是否有权限 |
| | | */ |
| | | export function hasPerm(path = "") { |
| | | export function hasPerm (path = "") { |
| | | if (!isPathExists(path) && path !== "/") { |
| | | uni.redirectTo({ |
| | | url: ERROR404_PATH |
| | |
| | | return hasPermission |
| | | } |
| | | |
| | | function setupPermission() { |
| | | function setupPermission () { |
| | | // 注意:拦截uni.switchTab本身没有问题。但是在微信小程序端点击tabbar的底层逻辑并不是触发uni.switchTab。 |
| | | // 所以误认为拦截无效,此类场景的解决方案是在tabbar页面的页面生命周期onShow中处理。 |
| | | ;["navigateTo", "redirectTo", "reLaunch", "switchTab"].forEach(item => { |
| | | // https://uniapp.dcloud.net.cn/api/interceptor.html |
| | | uni.addInterceptor(item, { |
| | | // 页面跳转前进行拦截, invoke根据返回值进行判断是否继续执行跳转 |
| | | invoke(args) { |
| | | invoke (args) { |
| | | // args为所拦截api中的参数,比如拦截的是uni.redirectTo(OBJECT),则args对应的是OBJECT参数 |
| | | return hasPerm(args.url) |
| | | } |
| | |
| | | import uviewPlus, { setConfig } from "uview-plus" |
| | | |
| | | function setupUI(app) { |
| | | function setupUI (app) { |
| | | // 下面的在特殊场景下才需要配置,通常不用配置即可直接使用uview-plus框架。 |
| | | // 调用setConfig方法,方法内部会进行对象属性深度合并,可以放心嵌套配置 |
| | | // 需要在app.use(uview-plus)之后执行 |
| | |
| | | * @param {object} pagesJson |
| | | * @returns [{"path": "/pages/tab/home/index","needLogin": false},...] |
| | | */ |
| | | function parseRoutes(pagesJson = {}) { |
| | | function parseRoutes (pagesJson = {}) { |
| | | if (!pagesJson.pages) { |
| | | pagesJson.pages = [] |
| | | } |
| | |
| | | pagesJson.subPackages = [] |
| | | } |
| | | |
| | | function parsePages(pages = [], rootPath = "") { |
| | | function parsePages (pages = [], rootPath = "") { |
| | | const routes = [] |
| | | for (let i = 0; i < pages.length; i++) { |
| | | routes.push({ |
| | |
| | | return routes |
| | | } |
| | | |
| | | function parseSubPackages(subPackages = []) { |
| | | function parseSubPackages (subPackages = []) { |
| | | const routes = [] |
| | | for (let i = 0; i < subPackages.length; i++) { |
| | | routes.push(...parsePages(subPackages[i].pages, subPackages[i].root)) |
| | |
| | | * 当前路由 |
| | | * @returns {string} 当前路由 |
| | | */ |
| | | export function currentRoute() { |
| | | export function currentRoute () { |
| | | // getCurrentPages() 至少有1个元素,所以不再额外判断 |
| | | const pages = getCurrentPages() |
| | | const currentPage = pages[pages.length - 1] |
| | |
| | | * @param {string} path |
| | | * @returns {string} 去除查询字符串后的路径 |
| | | */ |
| | | export function removeQueryString(path = "") { |
| | | export function removeQueryString (path = "") { |
| | | return path.split("?")[0] |
| | | } |
| | | |
| | |
| | | * @param {string} path |
| | | * @returns {boolean} 路径是否存在 |
| | | */ |
| | | export function isPathExists(path = "") { |
| | | export function isPathExists (path = "") { |
| | | const cleanPath = removeQueryString(path) |
| | | return routes.some(item => item.path === cleanPath) |
| | | } |
| | |
| | | * @param {string} path |
| | | * @returns {boolean} 是否是tabbar页面 |
| | | */ |
| | | export function isTabBarPath(path = "") { |
| | | export function isTabBarPath (path = "") { |
| | | const cleanPath = removeQueryString(path) |
| | | return ( |
| | | pagesJson.tabBar?.list?.some(item => `/${item.pagePath}` === cleanPath) === |
| | |
| | | import useUserStore from "./modules/user" |
| | | |
| | | // 安装pinia状态管理插件 |
| | | function setupStore(app) { |
| | | function setupStore (app) { |
| | | const store = createPinia() |
| | | |
| | | const piniaPersist = createPersistedState({ |
| | |
| | | import { defineStore } from 'pinia'; |
| | | import storage from '@/utils/storage'; |
| | | import { defineStore } from 'pinia' |
| | | import storage from '@/utils/storage' |
| | | |
| | | // 缓存的主题 |
| | | const THEME_KEY = 'app-theme'; |
| | | const THEME_KEY = 'app-theme' |
| | | |
| | | const useAppStore = defineStore('app', { |
| | | state: () => ({ |
| | |
| | | theme: storage.get(THEME_KEY) || 'light' |
| | | }), |
| | | getters: { |
| | | getSystemInfo(state) { |
| | | return state.systemInfo; |
| | | getSystemInfo (state) { |
| | | return state.systemInfo |
| | | }, |
| | | getTheme(state) { |
| | | return state.theme; |
| | | getTheme (state) { |
| | | return state.theme |
| | | } |
| | | }, |
| | | actions: { |
| | | setSystemInfo(info) { |
| | | this.systemInfo = info; |
| | | setSystemInfo (info) { |
| | | this.systemInfo = info |
| | | }, |
| | | initSystemInfo() { |
| | | initSystemInfo () { |
| | | uni.getSystemInfo({ |
| | | success: (res) => { |
| | | this.setSystemInfo(res); |
| | | this.setSystemInfo(res) |
| | | }, |
| | | fail: (err) => { |
| | | console.error(err); |
| | | console.error(err) |
| | | } |
| | | }); |
| | | }) |
| | | }, |
| | | /** |
| | | * 设置主题 |
| | | */ |
| | | setTheme(theme) { |
| | | this.theme = theme; |
| | | setTheme (theme) { |
| | | this.theme = theme |
| | | // 保存到本地存储 |
| | | storage.set(THEME_KEY, this.theme); |
| | | storage.set(THEME_KEY, this.theme) |
| | | }, |
| | | checkUpdate() { |
| | | const updateManager = uni.getUpdateManager(); |
| | | checkUpdate () { |
| | | const updateManager = uni.getUpdateManager() |
| | | updateManager.onCheckForUpdate((res) => { |
| | | // 请求完新版本信息的回调 |
| | | console.log(res.hasUpdate); |
| | | }); |
| | | console.log(res.hasUpdate) |
| | | }) |
| | | updateManager.onUpdateReady(() => { |
| | | uni.showModal({ |
| | | title: '更新提示', |
| | | content: '新版本已经准备好,是否重启应用?', |
| | | success(res) { |
| | | success (res) { |
| | | if (res.confirm) { |
| | | // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启 |
| | | updateManager.applyUpdate(); |
| | | updateManager.applyUpdate() |
| | | } |
| | | } |
| | | }); |
| | | }); |
| | | }) |
| | | }) |
| | | updateManager.onUpdateFailed((res) => { |
| | | console.error(res); |
| | | console.error(res) |
| | | // 新的版本下载失败 |
| | | uni.showToast({ |
| | | title: '更新失败', |
| | | icon: 'error' |
| | | }); |
| | | }); |
| | | }) |
| | | }) |
| | | } |
| | | } |
| | | }); |
| | | }) |
| | | |
| | | export default useAppStore; |
| | | export default useAppStore |
| | |
| | | * Copyright 2025 OBKoro1, All Rights Reserved. |
| | | * 2025-09-28 09:31:16 |
| | | */ |
| | | import { defineStore } from 'pinia'; |
| | | import { UserApi } from '@/api'; |
| | | import { clearToken, setToken } from '@/utils/auth'; |
| | | import { defineStore } from 'pinia' |
| | | import { UserApi } from '@/api' |
| | | import { clearToken, setToken } from '@/utils/auth' |
| | | |
| | | const useUserStore = defineStore('user', { |
| | | state: () => ({ |
| | |
| | | token: '' |
| | | }), |
| | | getters: { |
| | | userInfo(state) { |
| | | return { ...state }; |
| | | userInfo (state) { |
| | | return { ...state } |
| | | } |
| | | }, |
| | | actions: { |
| | | // 设置用户的信息 |
| | | setInfo(partial) { |
| | | this.$patch(partial); |
| | | setInfo (partial) { |
| | | this.$patch(partial) |
| | | }, |
| | | // 重置用户信息 |
| | | resetInfo() { |
| | | this.$reset(); |
| | | resetInfo () { |
| | | this.$reset() |
| | | }, |
| | | // 获取用户信息 |
| | | async info() { |
| | | const result = await UserApi.profile(); |
| | | this.setInfo(result); |
| | | async info () { |
| | | const result = await UserApi.profile() |
| | | this.setInfo(result) |
| | | }, |
| | | // 异步登录并存储token |
| | | login(loginForm) { |
| | | login (loginForm) { |
| | | return new Promise((resolve, reject) => { |
| | | UserApi.login(loginForm) |
| | | .then((res) => { |
| | | const token = res.token; |
| | | const token = res.token |
| | | if (token) { |
| | | setToken(token); |
| | | setToken(token) |
| | | } |
| | | resolve(res); |
| | | resolve(res) |
| | | }) |
| | | .catch((error) => { |
| | | reject(error); |
| | | }); |
| | | }); |
| | | reject(error) |
| | | }) |
| | | }) |
| | | }, |
| | | // Logout |
| | | async logout() { |
| | | await UserApi.logout(); |
| | | this.resetInfo(); |
| | | clearToken(); |
| | | async logout () { |
| | | await UserApi.logout() |
| | | this.resetInfo() |
| | | clearToken() |
| | | }, |
| | | // 小程序授权登录 |
| | | authLogin(provider = 'weixin') { |
| | | authLogin (provider = 'weixin') { |
| | | return new Promise((resolve, reject) => { |
| | | uni.login({ |
| | | provider, |
| | | success: async (result) => { |
| | | if (result.code) { |
| | | const res = await UserApi.loginByCode({ code: result.code }); |
| | | resolve(res); |
| | | const res = await UserApi.loginByCode({ code: result.code }) |
| | | resolve(res) |
| | | } else { |
| | | reject(new Error(result.errMsg)); |
| | | reject(new Error(result.errMsg)) |
| | | } |
| | | }, |
| | | fail: (err) => { |
| | | console.error(`login error: ${err}`); |
| | | reject(err); |
| | | console.error(`login error: ${err}`) |
| | | reject(err) |
| | | } |
| | | }); |
| | | }); |
| | | }) |
| | | }) |
| | | } |
| | | }, |
| | | persist: true |
| | | }); |
| | | }) |
| | | |
| | | export default useUserStore; |
| | | export default useUserStore |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { HOME_PATH } from "@/router" |
| | | import { HOME_PATH } from "@/router"; |
| | | |
| | | function handleBack() { |
| | | uni.$u.route({ |
| | | type: "switchTab", |
| | | url: HOME_PATH |
| | | }) |
| | | url: HOME_PATH, |
| | | }); |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | |
| | | </view> |
| | | <view class="theme-bg-secondary overflow-hidden rounded-12rpx"> |
| | | <view |
| | | v-for="option in themeOptions" :key="option.value" |
| | | v-for="option in themeOptions" |
| | | :key="option.value" |
| | | class="flex items-center border-b px-5 py-7.5 transition-all duration-300" |
| | | :class="{ 'theme-bg-secondary': theme === option.value }" @click="setTheme(option.value)" |
| | | :class="{ 'theme-bg-secondary': theme === option.value }" |
| | | @click="setTheme(option.value)" |
| | | > |
| | | <view class="mr-5"> |
| | | <view class="text-48rpx c-primary" :class="option.icon" /> |
| | |
| | | </view> |
| | | <view class="theme-bg-secondary rounded-12rpx p-7.5"> |
| | | <view class="mb-7.5"> |
| | | <text class="theme-text text-28rpx font-medium"> |
| | | 示例内容 |
| | | </text> |
| | | <text class="theme-text text-28rpx font-medium"> 示例内容 </text> |
| | | </view> |
| | | <view class="mb-7.5 flex flex-wrap gap-5"> |
| | | <view class="min-w-120rpx flex-1"> |
| | |
| | | </view> |
| | | </view> |
| | | <view class="flex flex-col gap-2.5"> |
| | | <text class="theme-text text-28rpx font-medium"> |
| | | 主要文字颜色 |
| | | </text> |
| | | <text class="theme-text-content text-26rpx"> |
| | | 内容文字颜色 |
| | | </text> |
| | | <text class="theme-text-tips text-24rpx"> |
| | | 提示文字颜色 |
| | | </text> |
| | | <text class="theme-text text-28rpx font-medium"> 主要文字颜色 </text> |
| | | <text class="theme-text-content text-26rpx"> 内容文字颜色 </text> |
| | | <text class="theme-text-tips text-24rpx"> 提示文字颜色 </text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 快速切换按钮 --> |
| | | <view class="py-5 text-center"> |
| | | <u-button :text="isDark ? '切换到亮色主题' : '切换到暗色主题'" type="primary" @click="toggleTheme" /> |
| | | <u-button |
| | | :text="isDark ? '切换到亮色主题' : '切换到暗色主题'" |
| | | type="primary" |
| | | @click="toggleTheme" |
| | | /> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { useTheme } from "@/hooks" |
| | | import { useAppStore } from "@/store" |
| | | import { useTheme } from "@/hooks"; |
| | | import { useAppStore } from "@/store"; |
| | | |
| | | const { theme, isDark, setTheme, toggleTheme } = useTheme() |
| | | const { theme, isDark, setTheme, toggleTheme } = useTheme(); |
| | | |
| | | const appStore = useAppStore() |
| | | const themeClass = computed(() => `theme-${appStore.theme}`) |
| | | const appStore = useAppStore(); |
| | | const themeClass = computed(() => `theme-${appStore.theme}`); |
| | | |
| | | // 主题选项 |
| | | const themeOptions = [ |
| | |
| | | label: "亮色主题", |
| | | value: "light", |
| | | icon: "i-mdi-white-balance-sunny", |
| | | description: "适合明亮环境使用" |
| | | description: "适合明亮环境使用", |
| | | }, |
| | | { |
| | | label: "暗色主题", |
| | | value: "dark", |
| | | icon: "i-mdi-moon-and-stars", |
| | | description: "适合暗光环境使用" |
| | | } |
| | | ] |
| | | |
| | | description: "适合暗光环境使用", |
| | | }, |
| | | ]; |
| | | </script> |
| | |
| | | const TokenKey = "admin-token" |
| | | const TokenPrefix = "Bearer " |
| | | function isLogin() { |
| | | function isLogin () { |
| | | return !!uni.getStorageSync(TokenKey) |
| | | } |
| | | function getToken() { |
| | | function getToken () { |
| | | return uni.getStorageSync(TokenKey) |
| | | } |
| | | function setToken(token) { |
| | | function setToken (token) { |
| | | uni.setStorageSync(TokenKey, token) |
| | | } |
| | | function clearToken() { |
| | | function clearToken () { |
| | | uni.removeStorageSync(TokenKey) |
| | | } |
| | | export { clearToken, getToken, isLogin, setToken, TokenPrefix } |
| | |
| | | // 小程序更新检测 |
| | | export function mpUpdate() { |
| | | export function mpUpdate () { |
| | | const updateManager = uni.getUpdateManager() |
| | | updateManager.onCheckForUpdate(res => { |
| | | // 请求完新版本信息的回调 |
| | |
| | | uni.showModal({ |
| | | title: "更新提示", |
| | | content: "检测到新版本,是否下载新版本并重启小程序?", |
| | | success(res) { |
| | | success (res) { |
| | | if (res.confirm) { |
| | | // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启 |
| | | updateManager.applyUpdate() |
| | |
| | | * @param {string} content 提示内容 |
| | | * @param {object} option 配置 |
| | | */ |
| | | export function Toast(content, option = {}) { |
| | | export function Toast (content, option = {}) { |
| | | uni.showToast({ |
| | | title: content, |
| | | icon: "none", |
| | |
| | | * @param {string} content 提示内容 |
| | | * @param {object} option 配置 |
| | | */ |
| | | export function Dialog(content, option = {}) { |
| | | export function Dialog (content, option = {}) { |
| | | option.showCancel = false |
| | | return new Promise((resolve, reject) => { |
| | | uni.showModal({ |
| | |
| | | content, |
| | | showCancel: false, |
| | | confirmColor: "#1677FF", |
| | | success(res) { |
| | | success (res) { |
| | | if (res.confirm) resolve(res) |
| | | }, |
| | | fail() { |
| | | fail () { |
| | | reject(new Error("Alert 调用失败 !")) |
| | | }, |
| | | ...option |
| | |
| | | const http = new Request() |
| | | |
| | | // 引入拦截器配置 |
| | | export function setupRequest() { |
| | | export function setupRequest () { |
| | | http.setConfig(defaultConfig => { |
| | | /* defaultConfig 为默认全局配置 */ |
| | | defaultConfig.baseURL = import.meta.env.VITE_API_BASE_URL |
| | |
| | | responseInterceptors(http) |
| | | } |
| | | |
| | | export function request(config) { |
| | | export function request (config) { |
| | | return new Promise((resolve, reject) => { |
| | | http |
| | | .request(config) |
| | |
| | | }) |
| | | } |
| | | |
| | | export function get(url, config) { |
| | | export function get (url, config) { |
| | | return request({ ...config, url, method: "GET" }) |
| | | } |
| | | |
| | | export function post(url, config) { |
| | | export function post (url, config) { |
| | | return request({ ...config, url, method: "POST" }) |
| | | } |
| | | |
| | | export function upload(url, config) { |
| | | export function upload (url, config) { |
| | | return request({ ...config, url, method: "UPLOAD" }) |
| | | } |
| | | |
| | | export function download(url, config) { |
| | | export function download (url, config) { |
| | | return request({ ...config, url, method: "DOWNLOAD" }) |
| | | } |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | function requestInterceptors(http) { |
| | | function requestInterceptors (http) { |
| | | /** |
| | | * 请求拦截 |
| | | * @param {object} http |
| | |
| | | ) => Promise.reject(config) |
| | | ) |
| | | } |
| | | function responseInterceptors(http) { |
| | | function responseInterceptors (http) { |
| | | /** |
| | | * 响应拦截 |
| | | * @param {object} http |
| | |
| | | const storage = { |
| | | set(key, value) { |
| | | set (key, value) { |
| | | if (key !== null && value !== null) uni.setStorageSync(key, value) |
| | | }, |
| | | get(key) { |
| | | get (key) { |
| | | if (key === null) return null |
| | | |
| | | return uni.getStorageSync(key) |
| | | }, |
| | | setJSON(key, jsonValue) { |
| | | setJSON (key, jsonValue) { |
| | | if (jsonValue !== null) this.set(key, JSON.stringify(jsonValue)) |
| | | }, |
| | | getJSON(key) { |
| | | getJSON (key) { |
| | | const value = this.get(key) |
| | | if (value) return JSON.parse(value) |
| | | }, |
| | | remove(key) { |
| | | remove (key) { |
| | | uni.removeStorageSync(key) |
| | | } |
| | | } |