小程序初始提交
This commit is contained in:
84
cool-unix/components/locale-set.uvue
Normal file
84
cool-unix/components/locale-set.uvue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<cl-select
|
||||
ref="selectRef"
|
||||
v-model="active"
|
||||
:options="options"
|
||||
:show-trigger="false"
|
||||
:title="t('切换语言')"
|
||||
:cancel-text="t('取消')"
|
||||
:confirm-text="t('确定')"
|
||||
></cl-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { locale, setLocale, t } from "@/locale";
|
||||
import { useUi, type ClSelectOption } from "@/uni_modules/cool-ui";
|
||||
import { ref } from "vue";
|
||||
|
||||
const ui = useUi();
|
||||
|
||||
// 语言列表
|
||||
const options = [
|
||||
{
|
||||
label: "简体中文",
|
||||
value: "zh-cn"
|
||||
},
|
||||
{
|
||||
label: "繁体中文",
|
||||
value: "zh-tw"
|
||||
},
|
||||
{
|
||||
label: "English",
|
||||
value: "en"
|
||||
},
|
||||
{
|
||||
label: "Español",
|
||||
value: "es"
|
||||
},
|
||||
{
|
||||
label: "日本語",
|
||||
value: "ja"
|
||||
},
|
||||
{
|
||||
label: "한국어",
|
||||
value: "ko"
|
||||
},
|
||||
{
|
||||
label: "Français",
|
||||
value: "fr"
|
||||
}
|
||||
] as ClSelectOption[];
|
||||
|
||||
const selectRef = ref<ClSelectComponentPublicInstance | null>(null);
|
||||
|
||||
// 当前语言
|
||||
const active = ref(locale.value);
|
||||
|
||||
// 打开
|
||||
function open() {
|
||||
active.value = locale.value;
|
||||
|
||||
if (["zh-Hans", "zh"].some((e) => e == locale.value)) {
|
||||
active.value = "zh-cn";
|
||||
}
|
||||
|
||||
selectRef.value!.open((value) => {
|
||||
ui.showLoading(t("切换中"));
|
||||
|
||||
setTimeout(() => {
|
||||
setLocale(value as string);
|
||||
ui.hideLoading();
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭
|
||||
function close() {
|
||||
selectRef.value!.close();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
close
|
||||
});
|
||||
</script>
|
||||
94
cool-unix/components/size-set.uvue
Normal file
94
cool-unix/components/size-set.uvue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<cl-select
|
||||
ref="selectRef"
|
||||
v-model="size"
|
||||
:title="t('全局字号')"
|
||||
:options="list"
|
||||
:show-trigger="false"
|
||||
@changing="onChanging"
|
||||
>
|
||||
<template #prepend>
|
||||
<view class="px-3 absolute top-0 left-0 z-10">
|
||||
<cl-text
|
||||
:style="{
|
||||
fontSize: 28 * size + 'rpx'
|
||||
}"
|
||||
>{{ t("这是一段示例文字,用于预览不同字号的效果。") }}</cl-text
|
||||
>
|
||||
</view>
|
||||
</template>
|
||||
</cl-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { t } from "@/locale";
|
||||
import { type ClSelectOption } from "@/uni_modules/cool-ui";
|
||||
import { config } from "@/uni_modules/cool-ui/config";
|
||||
import { ref } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "size-set"
|
||||
});
|
||||
|
||||
const selectRef = ref<ClSelectComponentPublicInstance | null>(null);
|
||||
|
||||
// 语言列表
|
||||
const list = [
|
||||
{
|
||||
label: "0.9",
|
||||
value: 0.9
|
||||
},
|
||||
{
|
||||
label: t("默认 1.0"),
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "1.1",
|
||||
value: 1.1
|
||||
},
|
||||
{
|
||||
label: "1.2",
|
||||
value: 1.2
|
||||
},
|
||||
{
|
||||
label: "1.3",
|
||||
value: 1.3
|
||||
},
|
||||
{
|
||||
label: "1.4",
|
||||
value: 1.4
|
||||
}
|
||||
] as ClSelectOption[];
|
||||
|
||||
// 当前语言
|
||||
const size = ref(1);
|
||||
|
||||
// 是否可见
|
||||
const visible = ref(false);
|
||||
|
||||
// 打开
|
||||
function open() {
|
||||
visible.value = true;
|
||||
size.value = config.fontSize ?? 1;
|
||||
|
||||
selectRef.value!.open((value) => {
|
||||
config.fontSize = value == 1 ? null : (value as number);
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭
|
||||
function close() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 切换
|
||||
function onChanging(value: number) {
|
||||
size.value = value;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
visible,
|
||||
open,
|
||||
close
|
||||
});
|
||||
</script>
|
||||
234
cool-unix/components/sms-btn.uvue
Normal file
234
cool-unix/components/sms-btn.uvue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<slot :disabled="isDisabled" :countdown="countdown" :btnText="btnText">
|
||||
<cl-button text :disabled="isDisabled" @tap="open">
|
||||
{{ btnText }}
|
||||
</cl-button>
|
||||
</slot>
|
||||
|
||||
<cl-popup
|
||||
v-model="captcha.visible"
|
||||
ref="popupRef"
|
||||
direction="center"
|
||||
:title="t('获取短信验证码')"
|
||||
:size="500"
|
||||
>
|
||||
<view class="p-3 pt-2 pb-4 w-full" v-if="captcha.visible">
|
||||
<view class="flex flex-row items-center">
|
||||
<cl-input
|
||||
v-model="code"
|
||||
:placeholder="t('验证码')"
|
||||
:maxlength="4"
|
||||
autofocus
|
||||
:clearable="false"
|
||||
:pt="{
|
||||
className: 'flex-1 mr-2 !h-[70rpx]'
|
||||
}"
|
||||
@confirm="send"
|
||||
></cl-input>
|
||||
|
||||
<view
|
||||
class="dark:!bg-surface-800 bg-surface-100 rounded-lg h-[70rpx] w-[200rpx] flex flex-row justify-center items-center"
|
||||
@tap="getCaptcha"
|
||||
>
|
||||
<cl-loading v-if="captcha.loading" :size="28"></cl-loading>
|
||||
<cl-svg
|
||||
v-else
|
||||
class="h-full w-full pointer-events-none"
|
||||
color="none"
|
||||
:src="captcha.img"
|
||||
></cl-svg>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<cl-button
|
||||
type="primary"
|
||||
:disabled="code == ''"
|
||||
:loading="captcha.sending"
|
||||
:pt="{
|
||||
className: '!h-[70rpx] mt-3'
|
||||
}"
|
||||
@tap="send"
|
||||
>
|
||||
{{ t("发送短信") }}
|
||||
</cl-button>
|
||||
</view>
|
||||
</cl-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { useUi } from "@/uni_modules/cool-ui";
|
||||
import { $t, t } from "@/locale";
|
||||
import { isDark, parse, request, type Response } from "@/cool";
|
||||
|
||||
const props = defineProps({
|
||||
phone: String
|
||||
});
|
||||
|
||||
const emit = defineEmits(["success"]);
|
||||
|
||||
const popupRef = ref<ClPopupComponentPublicInstance | null>(null);
|
||||
|
||||
const ui = useUi();
|
||||
|
||||
type Captcha = {
|
||||
visible: boolean;
|
||||
loading: boolean;
|
||||
sending: boolean;
|
||||
img: string;
|
||||
};
|
||||
|
||||
// 验证码
|
||||
const captcha = reactive<Captcha>({
|
||||
visible: false,
|
||||
loading: false,
|
||||
sending: false,
|
||||
img: ""
|
||||
});
|
||||
|
||||
// 倒计时
|
||||
const countdown = ref(0);
|
||||
|
||||
// 是否禁用
|
||||
const isDisabled = computed(() => countdown.value > 0 || props.phone == "");
|
||||
|
||||
// 按钮文案
|
||||
const btnText = computed(() =>
|
||||
countdown.value > 0 ? $t("{n}s后重新获取", { n: countdown.value }) : t("获取验证码")
|
||||
);
|
||||
|
||||
const code = ref("");
|
||||
const captchaId = ref("");
|
||||
|
||||
// 清空
|
||||
function clear() {
|
||||
code.value = "";
|
||||
captchaId.value = "";
|
||||
}
|
||||
|
||||
// 关闭
|
||||
function close() {
|
||||
captcha.visible = false;
|
||||
captcha.img = "";
|
||||
clear();
|
||||
}
|
||||
|
||||
// 开始倒计时
|
||||
function startCountdown() {
|
||||
countdown.value = 60;
|
||||
|
||||
let timer: number = 0;
|
||||
|
||||
function fn() {
|
||||
countdown.value--;
|
||||
|
||||
if (countdown.value < 1) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
timer = setInterval(() => {
|
||||
fn();
|
||||
}, 1000);
|
||||
fn();
|
||||
}
|
||||
|
||||
// 获取图片验证码
|
||||
async function getCaptcha() {
|
||||
clear();
|
||||
captcha.loading = true;
|
||||
|
||||
type Res = {
|
||||
captchaId: string;
|
||||
data: string;
|
||||
};
|
||||
|
||||
await request({
|
||||
url: "/app/user/login/captcha",
|
||||
data: {
|
||||
color: isDark.value ? "#ffffff" : "#2c3142",
|
||||
phone: props.phone,
|
||||
width: 200,
|
||||
height: 70
|
||||
}
|
||||
})
|
||||
.then((res) => {
|
||||
if (res != null) {
|
||||
const data = parse<Res>(res)!;
|
||||
|
||||
captchaId.value = data.captchaId;
|
||||
captcha.img = data.data;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
ui.showToast({
|
||||
message: (err as Response).message!
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
captcha.loading = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// 发送短信
|
||||
async function send() {
|
||||
if (code.value != "") {
|
||||
captcha.sending = true;
|
||||
|
||||
await request({
|
||||
url: "/app/user/login/smsCode",
|
||||
method: "POST",
|
||||
data: {
|
||||
phone: props.phone,
|
||||
code: code.value,
|
||||
captchaId: captchaId.value
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
ui.showToast({
|
||||
message: t("短信已发送,请查收")
|
||||
});
|
||||
startCountdown();
|
||||
close();
|
||||
emit("success");
|
||||
})
|
||||
|
||||
.catch((err) => {
|
||||
ui.showToast({
|
||||
message: (err as Response).message!
|
||||
});
|
||||
|
||||
getCaptcha();
|
||||
});
|
||||
|
||||
captcha.sending = false;
|
||||
} else {
|
||||
ui.showToast({
|
||||
message: t("请填写验证码")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 打开
|
||||
function open() {
|
||||
if (props.phone != "") {
|
||||
if (/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(props.phone!)) {
|
||||
captcha.visible = true;
|
||||
getCaptcha();
|
||||
} else {
|
||||
ui.showToast({
|
||||
message: t("请填写正确的手机号格式")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
send,
|
||||
getCaptcha,
|
||||
startCountdown
|
||||
});
|
||||
</script>
|
||||
83
cool-unix/components/tabbar.uvue
Normal file
83
cool-unix/components/tabbar.uvue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<cl-footer
|
||||
:pt="{
|
||||
content: {
|
||||
className: '!p-0 h-[60px]'
|
||||
}
|
||||
}"
|
||||
>
|
||||
<view class="custom-tabbar" :class="{ 'is-dark': isDark }">
|
||||
<view
|
||||
class="custom-tabbar-item"
|
||||
v-for="item in list"
|
||||
:key="item.pagePath"
|
||||
@tap="router.to(item.pagePath)"
|
||||
>
|
||||
<cl-image
|
||||
:src="path == item.pagePath ? item.icon2 : item.icon"
|
||||
:height="56"
|
||||
:width="56"
|
||||
></cl-image>
|
||||
|
||||
<cl-text
|
||||
v-if="item.text != null"
|
||||
:pt="{
|
||||
className: parseClass([
|
||||
'text-xs mt-1',
|
||||
[path == item.pagePath, 'text-primary-500', 'text-surface-400']
|
||||
])
|
||||
}"
|
||||
>{{ t(item.text!) }}</cl-text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</cl-footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ctx, isDark, parseClass, router } from "@/cool";
|
||||
import { t } from "@/locale";
|
||||
import { computed } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "custom-tabbar"
|
||||
});
|
||||
|
||||
type Item = {
|
||||
icon: string;
|
||||
icon2: string;
|
||||
pagePath: string;
|
||||
text: string | null;
|
||||
};
|
||||
|
||||
const path = computed(() => router.path());
|
||||
|
||||
// tabbar 列表
|
||||
const list = computed<Item[]>(() => {
|
||||
return (ctx.tabBar.list ?? []).map((e) => {
|
||||
return {
|
||||
icon: e.iconPath!,
|
||||
icon2: e.selectedIconPath!,
|
||||
pagePath: e.pagePath,
|
||||
text: t(e.text?.replaceAll("%", "")!)
|
||||
} as Item;
|
||||
});
|
||||
});
|
||||
|
||||
// 隐藏原生 tabBar
|
||||
// #ifndef MP
|
||||
if (ctx.tabBar.list != null) {
|
||||
uni.hideTabBar();
|
||||
}
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-tabbar {
|
||||
@apply flex flex-row items-center flex-1;
|
||||
|
||||
&-item {
|
||||
@apply flex flex-col items-center justify-center flex-1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user