Merge branch 'test' into test_任务管理v2

* test: (29 commits)
  Revert "style: 更新 Ant Select 样式和高度调整"
  style: 更新 Ant Select 样式和高度调整
  style: 修正表单标签对齐问题并更新 Ant Select 样式
  style: 更新README
  feat: 更新 API 地址并优化代码格式
  docs:移除 README 中的 Ant Design Vue 相关说明
  style: 移除无用的日志输出代码
  style(highlight-textarea): 优化文本域样式和功能
  perf: 文本高亮组件替换
  style: 卡片样式调整
  feat: 更新 .gitignore 文件
  refactor(src): 修改消息类型定义
  feat(api): 添加中断智能体执行功能并优化代码注释
  feat: 添加停止代理任务功能并优化样式
  perf: 去掉获取agent信息的await
  feat: 头像裁剪
  perf: 发送验证码交互优化
  style: 删除无用文件
  feat: 修改密码
  feat: 修改昵称,修改手机号
  ...
This commit is contained in:
lq
2025-09-15 14:20:14 +08:00
46 changed files with 1644 additions and 805 deletions

View File

@ -0,0 +1,215 @@
<template>
<Modal
class="change-mobile-modal"
centered
v-model:open="visible"
title="修改手机号"
@cancel="onClose"
:footer="null"
>
<Form
:model="form"
ref="formRef"
:rules="formRules"
labelAlign="right"
:labelCol="{ span: 5 }"
:wrapperCol="{ span: 19 }"
>
<FormItem label="手机号" name="mobile">
<Input v-model:value="form.mobile" placeholder="请输入新的手机号" size="large" ref="inputRef" />
</FormItem>
<FormItem label="获取验证码" name="captcha">
<Input v-model:value="form.captcha" placeholder="请输入验证码" size="large" :maxlength="6">
<template #suffix>
<div class="w-79px flex justify-center whitespace-nowrap">
<span
class="color-#939499 font-family-regular text-16px font-400 lh-24px cursor-not-allowed"
:class="{
'!color-#6D4CFE': isLegalMobile || countdown > 0,
'!cursor-pointer': canGetCaptcha,
}"
@click="getCode"
>{{ countdown > 0 ? `${countdown}s` : hasGetCode ? '重新发送' : '发送验证码' }}</span
>
</div>
</template>
</Input>
</FormItem>
</Form>
<div class="flex justify-end mt-20px">
<Button class="mr-16px" size="large" @click="onClose">取消</Button>
<Button type="primary" size="large" :loading="submitLoading" @click="handleConfirm">确定</Button>
</div>
</Modal>
</template>
<script setup>
import { computed, ref } from 'vue';
import { Button, Form, FormItem, Input, message, Modal } from 'ant-design-vue';
import { postUpdateMobileCaptcha } from '@/api/all/login';
import { updateMobile } from '@/api/all';
import { useUserStore } from '@/stores';
const store = useUserStore();
const formRef = ref();
const inputRef = ref();
const visible = ref(false);
const timer = ref();
const isLegalMobile = ref(false);
const hasGetCode = ref(false);
const submitLoading = ref(false);
const countdown = ref(0);
const formRules = {
mobile: [
{
required: true,
validator: (_rule, value) => {
if (!value) {
isLegalMobile.value = false;
return Promise.reject('手机号不能为空');
}
if (value && !/^1[3-9]\d{9}$/.test(value)) {
isLegalMobile.value = false;
return Promise.reject('手机号格式不正确');
} else {
isLegalMobile.value = true;
return Promise.resolve();
}
},
trigger: ['blur'],
},
],
captcha: [
{
required: true,
validator: (_rule, value) => {
if (!value) {
return Promise.reject('请输入验证码');
}
if (!/^\d{6}$/.test(value)) {
return Promise.reject('验证码必须是6位数字');
} else {
return Promise.resolve();
}
},
trigger: ['blur'],
},
],
};
const form = ref({
mobile: '',
captcha: '',
});
const canGetCaptcha = computed(() => {
return isLegalMobile.value && countdown.value === 0;
});
// 打开弹窗
const open = () => {
visible.value = true;
nextTick(() => {
inputRef.value?.focus();
});
};
// 发送验证码
const getCode = async () => {
if (!canGetCaptcha.value) return;
formRef.value.validateFields('mobile').then(() => {
getCaptcha();
});
};
const getCaptcha = async () => {
try {
const { code, message: msg } = await postUpdateMobileCaptcha({ mobile: form.value.mobile });
if (code === 200) {
startCountdown();
message.success(msg);
}
} catch (error) {
// 重置倒计时
countdown.value = 0;
clearInterval(timer.value);
}
};
const startCountdown = () => {
countdown.value = 60;
hasGetCode.value = true;
timer.value = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearTimer();
}
}, 1000);
};
// 确定按钮点击
const handleConfirm = () => {
formRef.value.validate().then(async () => {
try {
submitLoading.value = true;
const { code } = await updateMobile(form.value);
if (code === 200) {
await store.getUserInfo();
message.success('修改成功!');
onClose();
}
} finally {
submitLoading.value = false;
}
});
};
const clearTimer = () => {
if (timer.value) {
clearInterval(timer.value);
timer.value = null;
}
};
// 取消按钮点击
const onClose = () => {
form.value = {
mobile: '',
captcha: '',
};
countdown.value = 0;
visible.value = false;
hasGetCode.value = false;
isLegalMobile.value = false;
submitLoading.value = false;
formRef.value.resetFields();
formRef.value.clearValidate();
clearTimer();
};
// 暴露方法供外部调用
defineExpose({
open,
});
</script>
<style lang="scss">
.change-mobile-modal {
.ant-modal-header {
border-bottom: none !important;
}
.ant-modal-body {
padding: 20px 24px !important;
}
.ant-modal-footer {
height: 56px !important;
border-top: none !important;
}
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<Modal
class="change-name-modal"
v-model:open="visible"
centered
:width="480"
title="修改昵称"
:footer="null"
@cancel="onClose"
>
<Form :model="form" ref="formRef">
<FormItem label="昵称" name="name">
<Input v-model:value="form.name" placeholder="请输入新昵称" size="large" ref="inputRef" />
</FormItem>
</Form>
<div class="flex justify-end mt-20px">
<Button class="mr-16px" size="large" @click="onClose">取消</Button>
<Button type="primary" size="large" :loading="submitLoading" @click="handleNicknameConfirm">确定</Button>
</div>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, Form, FormItem, Input, message, Modal } from 'ant-design-vue';
import { updateMyInfo } from '@/api/all';
import { useUserStore } from '@/stores';
const store = useUserStore();
const formRef = ref();
const inputRef = ref();
const visible = ref(false);
const submitLoading = ref(false);
const form = ref({
name: '',
});
// 打开弹窗
const open = (userName) => {
form.value.name = userName;
visible.value = true;
nextTick(() => {
inputRef.value?.focus();
});
};
// 确定按钮点击
const handleNicknameConfirm = async () => {
try {
submitLoading.value = true;
const { code } = await updateMyInfo(form.value);
if (code === 200) {
await store.getUserInfo();
message.success('修改成功!');
onClose();
}
} finally {
submitLoading.value = false;
}
};
// 取消按钮点击
const onClose = () => {
form.value.name = '';
visible.value = false;
submitLoading.valeu = false;
};
// 暴露方法供外部调用
defineExpose({
open,
});
</script>
<style lang="scss">
.change-name-modal {
.ant-modal-header {
border-bottom: none !important;
}
.ant-modal-body {
padding: 20px 24px !important;
}
.ant-modal-footer {
height: 56px !important;
border-top: none !important;
}
}
</style>

View File

@ -0,0 +1,231 @@
<template>
<Modal
v-model:open="visible"
:title="title"
:width="412"
centered
:footer="null"
class="change-password-modal"
@cancel="onClose"
>
<div class="modal-content">
<p class="title-text">安全验证</p>
<template v-if="isCheckPass">
<p class="cts mt-36px !color-#211F24 mb-24px">验证成功请更改密码</p>
<Form ref="formRef" :model="formData" :rules="formRules" auto-label-width class="w-348 form-wrap">
<FormItem name="password" class="password-form-item">
<Input.Password v-model:value="formData.password" placeholder="新密码" size="large" class="!h-48px">
<template #iconRender="visible">
<img :src="visible ? icon2 : icon1" width="20" height="20" class="cursor-pointer" />
</template>
</Input.Password>
</FormItem>
<FormItem name="confirm_password" class="password-form-item">
<Input.Password
v-model:value="formData.confirm_password"
placeholder="密码确认"
size="large"
class="!h-48px"
>
<template #iconRender="visible">
<img :src="visible ? icon2 : icon1" width="20" height="20" class="cursor-pointer" />
</template>
</Input.Password>
</FormItem>
</Form>
<Button
type="primary"
:disabled="disabledSubmitBtn"
:loading="submitLoading"
class="w-full !h-48px mt-36px !rounded-8px"
@click="handleSubmit"
>完成更改
</Button>
</template>
<template v-else>
<p class="cts text-center mt-16px">为进一步保证您的账号安全确保为您本人操作</p>
<p class="cts mb-36px text-center">请先完成安全验证</p>
<p class="cts !color-#211F24 mb-24px">请输入发送至{{ showMobile }} 的短信验证码</p>
<div class="verify-code-wrap mb-24px">
<VerificationCode ref="verificationCodeRef" @success="onCheckPass" />
</div>
<p class="cts !text-12px !lh-20px !color-#939499" v-if="countdown > 0">
{{ countdown }} 秒后您可以再次发送验证码
</p>
<Button type="text" class="pl-0" v-if="hasGetCode && countdown === 0" @click="getCaptcha">重新发送</Button>
</template>
</div>
<PuzzleVerification
:show="isVerificationVisible"
@submit="handleVerificationSubmit"
@cancel="isVerificationVisible = false"
/>
</Modal>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Button, Form, FormItem, Input, message, Modal } from 'ant-design-vue';
import VerificationCode from '../verification-code/index.vue';
import PuzzleVerification from '@/views/login/components/PuzzleVerification.vue';
import { postUpdatePasswordCaptcha, postUpdatePassword } from '@/api/all/login';
import { postClearRateLimiter } from '@/api/all/common';
import icon1 from '@/assets/img/login/icon-close.png';
import icon2 from '@/assets/img/login/icon-open.png';
const emit = defineEmits(['success', 'cancel']);
const visible = ref(false);
const phone = ref('');
const isCheckPass = ref(false);
const formRef = ref(null);
const isVerificationVisible = ref(false);
// 验证码输入相关
const countdown = ref(0);
const timer = ref<number | null>(null);
const hasGetCode = ref(false);
const submitLoading = ref(false);
const verificationCodeRef = ref(null);
const formData = ref({
captcha: '',
password: '',
confirm_password: '',
});
const formRules = {
password: [
{
required: true,
validator: (_rule, value) => {
if (formData.value.confirm_password) {
formRef.value.validateFields('confirm_password');
}
return Promise.resolve();
},
trigger: ['blur'],
},
],
confirm_password: [
{
required: true,
validator: (_rule, value) => {
if (value !== formData.value.password) {
return Promise.reject('确认密码与设置的密码不同');
}
return Promise.resolve();
},
trigger: ['blur'],
},
],
};
const title = computed(() => (isCheckPass.value ? '更改密码' : '安全验证'));
const showMobile = computed(() => {
return phone.value?.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
});
const isPassPassword = computed(() => {
return (
formData.value.confirm_password &&
formData.value.password &&
formData.value.confirm_password === formData.value.password
);
});
const disabledSubmitBtn = computed(() => {
return !isPassPassword.value || !formData.value.password.trim();
});
const onCheckPass = (captcha) => {
isCheckPass.value = true;
formData.value.captcha = captcha;
};
const getCaptcha = async () => {
try {
const { code, message: msg } = await postUpdatePasswordCaptcha();
if (code === 200) {
startCountdown();
message.success(msg);
}
} catch (error) {
// 重置倒计时
countdown.value = 0;
clearInterval(timer.value);
if (error.status === 429) {
isVerificationVisible.value = true;
}
}
};
const handleVerificationSubmit = async () => {
isVerificationVisible.value = false;
verificationCodeRef.value?.init();
await postClearRateLimiter();
getCaptcha();
};
// 开始倒计时
const startCountdown = () => {
countdown.value = 60;
hasGetCode.value = true;
timer.value = window.setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearTimer();
}
}, 1000);
};
const clearTimer = () => {
if (timer.value) {
clearInterval(timer.value);
timer.value = null;
}
};
// 提交验证
const handleSubmit = async () => {
try {
submitLoading.value = true;
const { code } = await postUpdatePassword(formData.value);
if (code === 200) {
message.success('更改成功');
onClose();
}
} finally {
submitLoading.value = false;
}
};
const open = (mobile) => {
getCaptcha();
phone.value = mobile;
visible.value = true;
nextTick(() => {
verificationCodeRef.value?.init();
});
};
const onClose = () => {
visible.value = false;
phone.value = '';
countdown.value = 0;
hasGetCode.value = false;
submitLoading.value = false;
formRef.value?.resetFields();
formRef.value?.clearValidate();
clearTimer();
};
defineExpose({
open,
});
</script>
<style lang="scss">
@import './style.scss';
</style>

View File

@ -0,0 +1,62 @@
.change-password-modal {
.ant-modal-content {
border-radius: 16px;
.ant-modal-header {
border-radius: 16px 16px 0 0;
border-bottom: none !important;
.ant-modal-title {
display: none;
}
}
.ant-modal-body {
padding: 0 32px 8px !important;
position: relative;
top: -24px;
display: flex;
flex-direction: column;
justify-content: center;
.cts {
color: var(--Text-3, #737478);
font-family: $font-family-regular;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px; /* 157.143% */
}
.title-text {
color: var(--Text-1, #211F24);
text-align: center;
font-family: $font-family-medium;
font-size: 24px;
font-style: normal;
font-weight: 500;
line-height: 32px;
}
.ant-form {
.ant-form-item {
&:not(:last-child) {
margin-bottom: 24px !important;
}
.ant-input-affix-wrapper {
border-radius: 8px !important;
}
}
}
}
.ant-modal-footer {
height: 56px !important;
border-top: none !important;
}
}
}

View File

@ -0,0 +1,156 @@
<template>
<div class="verify-code-container">
<!-- 验证码输入区域 -->
<div class="code-inputs">
<Input
v-for="(item, index) in codeArray"
ref="inputRef"
:key="index"
:value="item.value"
:class="{ error: item.error, fill: item.value }"
@input="handleInput(index, $event.target.value)"
@focus="handleFocus(index)"
@blur="handleBlur(index)"
:maxlength="1"
placeholder=""
/>
</div>
<!-- 错误提示 -->
<p v-if="errorMessage" class="error-message">
{{ errorMessage }}
</p>
</div>
</template>
<script setup>
import { Input } from 'ant-design-vue';
import { ref, watch } from 'vue';
import { postCheckUpdatePasswordCaptcha } from '@/api/all/login';
const emits = defineEmits(['success']);
const codeArray = ref([]);
const activeIndex = ref(0); // 当前激活的输入框索引
const errorMessage = ref('');
const inputRef = ref(null);
// 处理输入事件
const handleInput = (index, value) => {
if (!value || !/^\d$/.test(value)) return;
codeArray.value[index].value = value;
if (index < 5) {
activeIndex.value = index + 1;
inputRef.value?.[activeIndex.value]?.focus?.();
}
};
const handleFocus = (index) => {
activeIndex.value = index;
};
const handleBlur = (index) => {
const _value = codeArray.value[index].value;
if (!_value || !/^\d$/.test(_value)) {
codeArray.value[index].error = true;
} else {
if (errorMessage.value) {
codeArray.value.forEach((item) => {
item.error = false;
});
}
errorMessage.value = '';
codeArray.value[index].error = false;
}
};
// 验证验证码逻辑
const validateCode = async () => {
const captcha = codeArray.value.map((item) => item.value).join('');
const { code } = await postCheckUpdatePasswordCaptcha({ captcha });
if (code === 200) {
emits('success', captcha);
} else {
activeIndex.value = 0;
inputRef.value?.[activeIndex.value]?.focus?.();
errorMessage.value = '验证码错误,请检查后重试';
codeArray.value.forEach((item) => {
item.error = true;
item.value = '';
});
}
};
const init = () => {
activeIndex.value = 0;
errorMessage.value = '';
inputRef.value = null;
codeArray.value = new Array(6).fill().map(() => ({ value: '', error: false }));
nextTick(() => {
inputRef.value?.[activeIndex.value]?.focus?.();
});
};
// 监听输入变化
watch(
() => codeArray.value,
(newVal) => {
if (newVal.every((item) => item.value)) {
validateCode();
}
},
{ deep: true },
);
defineExpose({ init });
</script>
<style scoped lang="scss">
.verify-code-container {
.code-inputs {
display: flex;
gap: 12px;
.ant-input {
width: 48px;
height: 48px;
border-radius: 8px !important;
text-align: center;
font-family: $font-family-medium;
font-size: 24px !important;
font-style: normal;
font-weight: 500;
line-height: 32px;
border: 1px solid #d7d7d9;
outline: none;
caret-color: #6d4cfe;
&:hover {
border-color: #6d4cfe !important;
}
&.fill {
color: #6d4cfe;
}
&.error {
border-color: #f64b31 !important;
}
}
}
}
.error-message {
color: var(--Functional-Red-6, #f64b31);
font-family: $font-family-regular;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px;
margin-top: 4px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,333 +1,126 @@
<template>
<div class="bg-#fff rounded-8px w-100% py-0 px-20px pb-24px">
<div class="title-row">
<span class="title">个人信息</span>
</div>
<Table :dataSource="dataSource" :pagination="false" :showSorterTooltip="false" class="mt-8px">
<Table.Column title="用户信息" dataIndex="info">
<template #customRender="{ record }">
<div class="pt-3px pb-3px">
<Avatar :src="record.head_image" :size="32" />
{{ record.name || '-' }}
<icon-edit size="13" class="ml-8px" @click="openEditInfoModal" />
</div>
</template>
</Table.Column>
<Table.Column title="手机号" dataIndex="mobile">
<template #customRender="{ record }">
{{ record.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}
<icon-edit size="13" class="ml-8px" @click="openEditMobileModal" />
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
</Table>
<Modal v-model:open="infoVisible" centered title="修改用户信息" @ok="handleSubmitUserInfo">
<Form
class="form"
:rules="rules"
:model="userInfoForm"
:label-col-props="{ span: 3, offset: 0 }"
:wrapper-col-props="{ span: 21, offset: 0 }"
<div class="bg-#fff rounded-16px w-100% p-36px person-wrap">
<p class="title mb-32px">个人信息</p>
<div class="flex items-center">
<Upload
action="/"
:showUploadList="false"
accept="image/*"
v-if="dataSource"
:customRequest="handleUpload"
class="mr-60px relative cursor-pointer"
>
<FormItem name="head_image" label="头像">
<Avatar :src="dataSource.head_image" :size="100" v-if="dataSource.head_image" />
<div v-else class="w-100 h-100 rounded-50% bg-#6D4CFE flex items-center justify-center">
<span class="color-#FFF text-46px font-400">{{ dataSource.mobile?.slice(-3) }}</span>
</div>
<img :src="icon1" width="20" height="20" class="absolute right-5px bottom-4px" />
</Upload>
<div class="flex-1 flex h-68px">
<div class="flex flex-1">
<span class="cts mr-4p">昵称</span>
<span class="cts !color-#211F24 bold !font-500 mr-12px">{{ dataSource.name || '-' }}</span>
<Button type="text" size="small" class="!p-0 !h-22px m-0" @click="openChangeNameModal">编辑</Button>
</div>
<div class="flex-1">
<div class="flex items-center mb-24px">
<span class="cts mr-4px w-56px">手机号</span>
<span class="cts !color-#211F24 bold !font-500 mr-12px">{{ mobileLabel }}</span>
<Button type="text" size="small" class="!p-0 !h-22px m-0" @click="openChangeMobileModal">更改</Button>
</div>
<div class="flex items-center">
<Avatar :src="userInfoForm.file_url" :size="48" />
<span class="upload-button" @click="triggerFileInput">
<input
ref="uploadInputRef"
accept="image/*"
type="file"
style="display: none"
@change="handleFileChange"
/>
<Button><icon-upload />上传新头像</Button>
</span>
<span class="cts mr-4px w-56px">密码</span>
<span
class="!bg-#211F24 bold !font-500 w-6px h-6px rounded-50% mr-2px"
v-for="(item, index) in new Array(10).fill(0)"
:key="index"
></span>
<Button type="text" size="small" class="!p-0 !h-22px ml-10px" @click="openChangePasswordModal">更改</Button>
</div>
</FormItem>
<FormItem name="name" label="昵称">
<Input v-model:value="userInfoForm.name" placeholder="请输入昵称" />
</FormItem>
</Form>
</Modal>
<Modal v-model:open="imageVisible" centered title="头像裁剪">
<VueCropper></VueCropper>
</Modal>
<Modal v-model:open="mobileVisible" centered title="修改手机号" @ok="handleUpdateMobile">
<Form
ref="formRef"
:model="form"
class="form"
:rules="formRules"
labelAlign="right"
:labelCol="{ span: 4 }"
:wrapperCol="{ span: 20 }"
>
<FormItem required name="mobile" label="新手机号">
<Input v-model:value="form.mobile" size="small" placeholder="请输入新的手机号" />
</FormItem>
<FormItem required name="captcha" label="获取验证码">
<Input v-model:value="form.captcha" size="small" placeholder="请输入验证码">
<template #suffix>
<span v-if="countdown <= 0" @click="sendCaptcha">发送验证码</span>
<span v-else>{{ countdown }}s</span>
</template>
</Input>
</FormItem>
</Form>
<PuzzleVerification
:show="verificationVisible"
@submit="handleVerificationSubmit"
@cancel="verificationVisible = false"
/>
</Modal>
</div>
</div>
</div>
<ChangePasswordModal ref="changePasswordModalRef" />
<ChangeNameModal ref="changeNameModalRef" />
<ChangeMobileModal ref="changeMobileModalRef" />
</div>
</template>
<script setup lang="ts">
import { Button, Form, FormItem, Input, Table, message, Avatar } from 'ant-design-vue';
import Container from '@/components/container.vue';
import Modal from '@/components/modal.vue';
import PuzzleVerification from '@/views/login/components/PuzzleVerification.vue';
import { ref, reactive } from 'vue';
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
import { sendUpdateMobileCaptcha, updateMobile, fetchImageUploadFile, updateMyInfo } from '@/api/all';
import { Avatar, Button, Upload, message } from 'ant-design-vue';
import ChangePasswordModal from './components/change-password-modal/index.vue';
import ChangeNameModal from './components/change-name-modal/index.vue';
import ChangeMobileModal from './components/change-mobile-modal/index.vue';
import { reactive, ref } from 'vue';
import axios from 'axios';
import { fetchImageUploadFile, updateMyInfo } from '@/api/all';
import { useUserStore } from '@/stores';
import icon1 from './img/icon1.png';
const store = useUserStore();
const userInfo = computed(() => {
return store.userInfo ?? {};
});
const columns = [
{
title: '用户信息',
slotName: 'info',
},
{
title: '手机号',
slotName: 'mobile',
},
];
const infoVisible = ref(false);
const imageVisible = ref(false);
const mobileVisible = ref(false);
const verificationVisible = ref(false);
const timer = ref();
const countdown = ref(0);
const changePasswordModalRef = ref(null);
const changeNameModalRef = ref(null);
const changeMobileModalRef = ref(null);
const formRef = ref();
const isSendCaptcha = ref(false);
const uploadInputRef = ref();
const dataSource = computed(() => {
return userInfo.value ? [userInfo.value] : [];
return store.userInfo;
});
const mobileLabel = computed(() => {
const _mobile = dataSource.value.mobile;
if (!_mobile) return '-';
return _mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
});
// 表单校验规则
const formRules = {
mobile: [
{
validator: (_rule: any, value: string) => {
if (!value) {
return Promise.reject('请填写手机号');
}
if (!/^1[3-9]\d{9}$/.test(value)) {
return Promise.reject('手机号格式不正确');
}
return Promise.resolve();
},
required: true,
trigger: ['blur', 'change'],
},
],
captcha: [
{
required: true,
validator: (rule, value) => {
if (!value) {
return Promise.reject('请填写验证码');
}
if (!/^\d{6}$/.test(value)) {
return Promise.reject('验证码必须是6位数字');
}
return Promise.resolve();
},
// const form = reactive({
// mobile: '',
// captcha: '',
// });
trigger: ['blur', 'change'],
},
],
const openChangeNameModal = () => {
changeNameModalRef.value.open(dataSource.name);
};
const openChangeMobileModal = () => {
changeMobileModalRef.value.open();
};
const openChangePasswordModal = () => {
changePasswordModalRef.value.open(dataSource.value.mobile);
};
const form = reactive({
mobile: '',
captcha: '',
});
const userInfoForm = reactive({
name: '',
head_image: '',
file_url: '',
});
function triggerFileInput() {
uploadInputRef.value.click();
}
function openEditInfoModal() {
infoVisible.value = true;
}
async function handleFileChange(event: Event) {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
if (file) {
const fileExtension = getFileExtension(file.name);
const { data } = await fetchImageUploadFile({
suffix: fileExtension,
});
const { upload_url, file_name, file_url } = data;
const blob = new Blob([file], { type: file.type });
await axios.put(upload_url, blob, {
headers: { 'Content-Type': file.type },
});
userInfoForm.head_image = file_name;
userInfoForm.file_url = file_url;
}
}
function openEditImageModal() {
imageVisible.value = true;
}
function openEditMobileModal() {
mobileVisible.value = true;
}
async function handleSubmitUserInfo() {
await updateMyInfo(userInfoForm);
message.success('修改成功!');
}
async function sendCaptcha() {
try {
const result = await formRef.value.validateField('mobile');
if (result === true || result === undefined) {
verificationVisible.value = true;
isSendCaptcha.value = true;
}
message.error('请填写正确的手机号!');
} catch (error) {
console.log('手机号验证失败:', error);
}
}
function beginCountdown() {
timer.value = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearInterval(timer.value);
timer.value = null;
}
}, 1000);
}
async function handleVerificationSubmit() {
await sendUpdateMobileCaptcha({ mobile: form.mobile });
message.success('发送成功');
verificationVisible.value = false;
countdown.value = 60;
beginCountdown();
}
async function handleUpdateMobile() {
if (!isSendCaptcha.value) {
message.error('请先获取验证码!');
return false;
}
const res = await formRef.value.validate();
if (res === true || res === undefined) {
await updateMobile(form);
message.success('修改成功!');
}
}
function getFileExtension(filename: string): string {
const getFileExtension: string = (filename: string) => {
const match = filename.match(/\.([^.]+)$/);
return match ? match[1].toLowerCase() : '';
}
};
const handleUpload = async (option) => {
const { file } = option;
try {
if (file) {
const fileExtension = getFileExtension(file.name);
const { data } = await fetchImageUploadFile({
suffix: fileExtension,
});
const { upload_url, file_url } = data;
const blob = new Blob([file], { type: file.type });
await axios.put(upload_url, blob, {
headers: { 'Content-Type': file.type },
});
const { code } = await updateMyInfo({ head_image: file_url });
if (code === 200) {
message.success('修改成功');
store.userInfo.head_image = file_url;
}
}
} catch (error) {
message.error('上传失败');
}
};
</script>
<style scoped lang="scss">
.form {
margin-top: 13px;
:deep(.arco-row) {
align-items: center;
}
:deep(.arco-form-item-label) {
font-family: $font-family-medium;
font-weight: 400;
font-size: 14px;
margin: 0;
}
:deep(.arco-input-wrapper) {
background: white;
border: 1px solid var(--BG-400, rgba(215, 215, 217, 1));
font-family: $font-family-medium;
font-weight: 400;
font-size: 14px;
padding: 4px 12px;
input::placeholder {
font-family: $font-family-medium;
font-weight: 400;
font-size: 14px;
color: var(--Text-4, rgba(147, 148, 153, 1));
}
}
:deep(.arco-input-disabled) {
background: var(--BG-200, rgba(242, 243, 245, 1));
border: 1px solid var(--BG-400, rgba(215, 215, 217, 1));
.arco-input:disabled {
font-family: $font-family-medium;
font-weight: 400;
font-size: 14px;
}
}
:deep(.arco-input-focus) {
border: 1px solid var(--Brand-Brand-6, rgba(109, 76, 254, 1));
box-shadow: 0 2px 4px 0 rgba(109, 76, 254, 0.2);
}
}
.upload-button {
width: 104px;
height: 24px;
margin-left: 12px;
:deep(.arco-btn) {
width: 104px;
height: 24px;
border-radius: 4px;
padding: 2px 12px;
border: 1px solid var(--BG-500, rgba(177, 178, 181, 1));
font-family: $font-family-medium;
font-weight: 400;
font-size: 12px;
vertical-align: middle;
color: var(--Text-2, rgba(60, 64, 67, 1));
}
}
.title-row {
display: flex;
height: 64px;
padding: 10px 0 2px 0;
align-items: center;
.title {
color: var(--Text-1, #211f24);
font-family: $font-family-medium;
font-size: 18px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 150% */
}
}
@import './style.scss';
</style>

View File

@ -0,0 +1,22 @@
.person-wrap {
.title {
color: var(--Text-1, #211f24);
font-family: $font-family-medium;
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 26px; /* 150% */
}
.cts {
color: var(--Text-4, #939499);
font-family: $font-family-regular;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px; /* 157.143% */
&.bold {
font-family: $font-family-medium;
}
}
}

View File

@ -98,43 +98,32 @@
</template>
<script setup lang="ts">
import {
Checkbox,
Modal,
Button,
Form,
FormItem,
Input,
Space,
message,
Typography,
Card,
List,
Tabs,
} from 'ant-design-vue';
const { Link } = Typography;
const { TabPane } = Tabs;
import { Button, Checkbox, Form, FormItem, Input, message, Tabs, Typography } from 'ant-design-vue';
import PuzzleVerification from '../PuzzleVerification.vue';
import SelectAccountModal from '../select-account-modal/index.vue';
import { fetchLoginCaptCha, fetchAuthorizationsCaptcha, fetchProfileInfo, postLoginPassword } from '@/api/all/login';
import { fetchAuthorizationsCaptcha, fetchLoginCaptCha, fetchProfileInfo, postLoginPassword } from '@/api/all/login';
import { postClearRateLimiter } from '@/api/all/common';
import { joinEnterpriseByInviteCode } from '@/api/all';
import { ref, reactive, onUnmounted, computed } from 'vue';
import { computed, onUnmounted, reactive, ref } from 'vue';
import { useUserStore } from '@/stores';
import { useEnterpriseStore } from '@/stores/modules/enterprise';
import { handleUserLogin, goUserLogin } from '@/utils/user';
import router from '@/router';
import { handleUserLogin } from '@/utils/user';
import { useRoute } from 'vue-router';
import icon1 from '@/assets/img/login/icon-close.png';
import icon2 from '@/assets/img/login/icon-open.png';
const { Link } = Typography;
const { TabPane } = Tabs;
const setPageType = inject('setPageType');
const formRef = ref();
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const enterpriseStore = useEnterpriseStore();
const formRef = ref();
const countdown = ref(0);
let timer = ref();
const isLogin = ref(true);
@ -277,7 +266,7 @@ const onTabChange = () => {
const getProfileInfo = async () => {
const { code, data } = await fetchProfileInfo();
if (code === 200) {
let enterprises = data['enterprises'];
const enterprises = data['enterprises'];
mobileNumber.value = data['mobile'];
accounts.value = enterprises;
@ -289,13 +278,14 @@ const getProfileInfo = async () => {
// 多个企业时候需要弹窗让用户选择企业
selectAccountModalRef.value.open();
}
} else {
router.push({ name: 'Trial' });
}
}
};
// 提交表单
const handleSubmit = async () => {
console.log('handleSubmit', disabledSubmitBtn.value);
if (disabledSubmitBtn.value) return;
try {
@ -312,7 +302,6 @@ const handleSubmit = async () => {
const _fn = isCaptchaLogin.value ? fetchAuthorizationsCaptcha : postLoginPassword;
const { code, data, message: errorInfo } = await _fn(loginForm);
console.log(code, errorInfo);
if (code === 10001) {
errMsg.value = errorInfo;
return;

View File

@ -85,44 +85,39 @@
</template>
<script setup lang="js">
import {
Checkbox,
Modal,
Button,
Form,
FormItem,
Input,
Space,
message,
Typography,
Card,
List,
Tabs,
} from 'ant-design-vue';
const { Link } = Typography;
const { TabPane } = Tabs;
import { Button, Checkbox, Form, FormItem, Input, message, Tabs, Typography } from 'ant-design-vue';
import PuzzleVerification from '../PuzzleVerification.vue';
import SelectAccountModal from '../select-account-modal/index.vue';
import { postClearRateLimiter } from '@/api/all/common';
import { postRegisterCaptcha,postForgetPasswordCaptcha, fetchProfileInfo, postRegister, postForgetPassword } from '@/api/all/login';
import {
fetchProfileInfo,
postForgetPassword,
postForgetPasswordCaptcha,
postRegister,
postRegisterCaptcha,
} from '@/api/all/login';
import { joinEnterpriseByInviteCode } from '@/api/all';
import { ref, reactive, onUnmounted, computed } from 'vue';
import { computed, onUnmounted, ref } from 'vue';
import { useUserStore } from '@/stores';
import { useEnterpriseStore } from '@/stores/modules/enterprise';
import { handleUserLogin, goUserLogin } from '@/utils/user';
import router from '@/router';
import { handleUserLogin } from '@/utils/user';
import { useRoute } from 'vue-router';
import icon1 from '@/assets/img/login/icon-close.png';
import icon2 from '@/assets/img/login/icon-open.png';
const { Link } = Typography;
const { TabPane } = Tabs;
const setPageType = inject('setPageType');
const pageType = inject('pageType');
const formRef = ref();
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const enterpriseStore = useEnterpriseStore();
const formRef = ref();
const countdown = ref(0);
let timer = ref();
const isLogin = ref(true);
@ -173,9 +168,9 @@ const formRules = {
// if (value.length < 6) {
// return Promise.reject('密码长度不能小于6位');
// }
if(formData.value.confirm_password) {
formRef.value.validateFields('confirm_password');
}
if (formData.value.confirm_password) {
formRef.value.validateFields('confirm_password');
}
return Promise.resolve();
},
trigger: ['blur'],
@ -268,7 +263,7 @@ const getCaptcha = async () => {
const fn = isResetPassword.value ? postForgetPasswordCaptcha : postRegisterCaptcha;
const { code, message: msg } = await fn({ mobile: formData.value.mobile });
if (code === 200) {
startCountdown()
startCountdown();
message.success(msg);
}
} catch (error) {
@ -293,19 +288,21 @@ const handleVerificationSubmit = async () => {
const getProfileInfo = async () => {
const { code, data } = await fetchProfileInfo();
if (code === 200) {
let enterprises = data['enterprises'];
const enterprises = data['enterprises'];
mobileNumber.value = data['mobile'];
accounts.value = enterprises;
if (enterprises.length > 0) {
enterpriseStore.setEnterpriseInfo(data.enterprises[0]);
if (enterprises.length === 1) {
setTimeout(() => {
handleUserLogin();
}, 1500);
setTimeout(() => {
handleUserLogin();
}, 1500);
} else {
selectAccountModalRef.value.open();
}
} else {
router.push({ name: 'Trial' });
}
}
};
@ -335,13 +332,14 @@ const handleSubmit = async () => {
if (code === 200) {
message.success(isResetPassword.value ? '重置成功' : '注册成功');
// 注册成功后跳转登录页
if(!isResetPassword.value) {
setTimeout(() => {
setPageType('loginForm');
}, 1500);
return;
};
// 注册成功后跳转登录页
if (!isResetPassword.value) {
setTimeout(() => {
setPageType('loginForm');
}, 1500);
return;
}
;
userStore.setToken(data.access_token);
@ -371,7 +369,7 @@ const startCountdown = () => {
timer.value = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearInterval(timer.value );
clearInterval(timer.value);
timer.value = null;
}
}, 1000);

View File

@ -1,12 +1,12 @@
<template>
<div class="highlight-textarea-container">
<a-textarea
<TextArea
ref="textareaWrapRef"
v-model="inputValue"
v-model:value="inputValue"
placeholder="请输入作品描述"
:disabled="disabled"
show-word-limit
:max-length="1000"
showCount
:maxlength="1000"
size="large"
class="textarea-input h-full w-full"
@input="handleInput"
@ -25,7 +25,8 @@
<script setup lang="ts">
import { Input } from 'ant-design-vue';
const {TextArea} = Input
const { TextArea } = Input;
import { ref, computed, watch, defineProps, defineEmits, onMounted, onUnmounted } from 'vue';
import { escapeRegExp } from './constants';
@ -69,8 +70,8 @@ watch(
);
// 处理输入事件
const handleInput = (value: string) => {
emit('update:modelValue', value);
const handleInput = (e) => {
emit('update:modelValue', e.currentTarget.value);
};
const escapeHtml = (str: string): string => {
@ -109,14 +110,12 @@ const generateHighlightedHtml = (): string => {
const levelStyle = props.levelMap?.get(wordInfo.risk_level);
const color = levelStyle?.color || '#F64B31';
return `<span class="text-14px font-400 lh-22px" style="color: ${color};">${escapeHtml(match)}</span>`;
return `<span class="text-14px font-400 lh-22px font-family-regular" style="color: ${color};">${escapeHtml(match)}</span>`;
});
};
onMounted(() => {
nativeTextarea =
(textareaWrapRef.value?.$el || textareaWrapRef.value)?.querySelector?.('textarea.arco-textarea') ||
document.querySelector('.textarea-input .arco-textarea');
nativeTextarea = textareaWrapRef.value?.$el?.querySelector?.('textarea.ant-input');
if (nativeTextarea) {
nativeTextarea.addEventListener('scroll', handleTextareaScroll);
@ -173,8 +172,9 @@ const handleCompositionUpdate = () => {
height: 100%;
font-size: 14px;
line-height: 22px;
font-weight: 400px;
border: 1px solid #d7d7d9;
font-weight: 400;
font-family: $font-family-regular;
//border: 1px solid #d7d7d9;
border-radius: 4px;
resize: none;
white-space: pre-wrap;
@ -192,43 +192,43 @@ const handleCompositionUpdate = () => {
background: #fff;
overflow: hidden;
@include textarea-padding;
&.focus {
border-color: rgb(var(--primary-6)) !important;
box-shadow: 0 0 0 0 var(--color-primary-light-2) !important;
}
}
:deep(.arco-textarea-wrapper) {
:deep(.ant-input-textarea) {
@include textarea-style;
.arco-textarea {
textarea {
padding: 0;
overflow: hidden;
position: absolute;
z-index: 1;
width: 100%;
background: transparent;
background-color: transparent !important;
color: transparent;
caret-color: #211f24 !important;
resize: none;
@include textarea-padding;
overflow-y: auto;
}
&.ant-input-disabled {
background: #f2f3f5 !important;
-webkit-text-fill-color: rgba(0, 0, 0, 0.25) !important;
}
.arco-textarea-word-limit {
z-index: 2;
}
&.arco-textarea-disabled {
.arco-textarea {
background: #f2f3f5;
&.ant-input-textarea-show-count {
&::after {
position: absolute;
z-index: 2;
font-size: 12px;
right: 10px;
bottom: 6px;
user-select: none;
}
}
}
// 处于中文输入法合成态时,显示真实文本并隐藏高亮层
:deep(.textarea-input.composing .arco-textarea) {
:deep(.ant-input-textarea.composing textarea) {
color: #211f24 !important;
-webkit-text-fill-color: #211f24 !important;
}

View File

@ -12,7 +12,7 @@
}
.card-item {
border-radius: 8px;
// border: 1px solid var(--BG-300, #e6e6e8);
border: 1px solid transparent;
background: var(--BG-white, #fff);
padding: 12px 16px 16px;
position: relative;
@ -103,7 +103,7 @@
padding-top: 8px;
}
&.checked {
border: 1px solid var(--Brand-6, #6d4cfe);
border-color: #6d4cfe;
}
}
}

View File

@ -297,7 +297,7 @@ export default {
model={form.value}
rules={rules}
layout="horizontal"
labelAlign="right"
labelAlign="right"
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
>

View File

@ -1,9 +1,9 @@
<template>
<Layout class="flex justify-center items-center trial-wrap">
<Layout.Header class="header-wrap">
<Layout.Header class="header-wrap cursor-pointer" @click="onLogoClick">
<div class="h-full px-24px">
<div class="w-full h-full relative flex justify-between">
<div class="flex items-center cursor-pointer" @click="handleUserHome">
<div class="flex items-center">
<img src="@/assets/img/icon-logo.png" alt="" width="96" height="24" />
</div>
<RightSide />
@ -13,7 +13,7 @@
<Layout class="flex trial-content items-center">
<div class="w-800px">
<!-- 未建联 -->
<section class="w-full" v-if="status === 1">
<section class="w-full" v-if="!primary_enterprise">
<div class="rounded-16px mb-16px bg-#fff px-24px py-16px flex justify-between">
<div class="flex items-center">
<span class="cts !text-18px !lh-26px !color-#000 mr-8px">申请试用</span>
@ -25,18 +25,18 @@
<div class="rounded-16px mb-16px bg-#fff p-24px">
<p class="cts !text-16px !lh-24px !color-#000 mb-32px">基本信息</p>
<Form ref="formRef" :model="formData" :rules="formRules" layout="vertical" class="w-full form-wrap">
<FormItem name="name" label="联系人">
<FormItem name="contact" label="联系人">
<Input
v-model:value="formData.name"
v-model:value="formData.contact"
placeholder="请输入您的姓名"
size="large"
allowClear
class="w-500px"
/>
</FormItem>
<FormItem name="company_name" label="公司名称">
<FormItem name="name" label="公司名称">
<Input
v-model:value="formData.company_name"
v-model:value="formData.name"
placeholder="请输入您的公司名称,个人用户写无"
size="large"
allowClear
@ -65,7 +65,7 @@
</section>
<!-- 建立商务联系中 -->
<section class="w-full" v-if="status === 2">
<section class="w-full" v-if="enterpriseStore.enterpriseInfo?.audit_status === 1">
<div class="rounded-16px bg-#fff px-36px pt-80px pb-60px flex flex-col items-center">
<img :src="icon2" width="96" height="96" class="mb-8px" />
<p class="cts !text-18px !lh-26px mb-8px">您的试用申请已提交</p>
@ -87,7 +87,7 @@
</section>
<!-- 试用到期 -->
<section class="w-full" v-if="status === 3">
<section class="w-full" v-if="enterpriseStore.enterpriseInfo?.subscribe_status === 4">
<div class="rounded-16px bg-#fff px-36px pt-80px pb-60px flex flex-col items-center">
<img :src="icon1" width="96" height="96" class="mb-8px" />
<p class="cts !text-18px !lh-26px mb-8px">试用已到期</p>
@ -113,26 +113,36 @@
</template>
<script setup lang="ts">
import { Layout, Form, Button, FormItem, Input, Steps } from 'ant-design-vue';
import { Button, Form, FormItem, Input, Layout, Steps } from 'ant-design-vue';
import RightSide from '@/layouts/components/navbar/components/right-side/index.vue';
import { useRouter } from 'vue-router';
import { postCreateEnterprises } from '@/api/all/login';
import { exactFormatTime } from '@/utils/tools';
import { handleUserHome } from '@/utils/user';
import { useUserStore } from '@/stores';
import { useEnterpriseStore } from '@/stores/modules/enterprise';
import icon1 from './img/icon-info.png';
import icon2 from './img/icon-check.png';
type Status = 1 | 2 | 3;
// 0-未开通1-已开通2-试用中3-已到期4-试用结束
type Status = 0 | 1 | 2 | 3 | 4;
const router = useRouter();
const userStore = useUserStore();
const enterpriseStore = useEnterpriseStore();
const formRef = ref();
const submitting = ref(false);
const status = ref<Status>(1);
// const status = ref<Status>(1);
const formData = ref({
mobile: '',
contact: '',
name: '',
company_name: '',
});
const formRules = {
name: {
contact: {
required: true,
trigger: ['blur'],
@ -144,7 +154,7 @@ const formRules = {
}
},
},
company_name: {
name: {
required: true,
trigger: ['blur'],
@ -170,11 +180,14 @@ const formRules = {
},
};
const primary_enterprise = computed(() => userStore.userInfo?.primary_enterprise);
const hasOpenEnterprise = computed(() => enterpriseStore.isOpenEnterprise);
const trialingStepsItems = computed(() => {
return [
{
title: '提交申请',
description: '2025/09/01 12:00:00',
description: exactFormatTime(enterpriseStore.enterpriseInfo?.created_at),
},
{
title: '人工审核',
@ -190,7 +203,7 @@ const trialEndStepsItems = computed(() => {
return [
{
title: '提交申请',
description: '2025/09/01 12:00:00',
description: exactFormatTime(enterpriseStore.enterpriseInfo?.created_at),
},
{
title: '人工审核',
@ -203,12 +216,30 @@ const trialEndStepsItems = computed(() => {
];
});
const onLogoClick = () => {
if (hasOpenEnterprise.value) {
handleUserHome();
} else {
router.push({
name: 'Trial',
});
}
};
const handleSubmit = async () => {
submitting.value = true;
formRef.value
.validate()
.then(() => {
console.log('验证通过');
.then(async () => {
const { code } = await postCreateEnterprises(formData.value);
if (code === 200) {
await userStore.getUserInfo();
const { primary_enterprise } = userStore.userInfo;
if (primary_enterprise) {
enterpriseStore.setEnterpriseInfo(primary_enterprise);
}
}
})
.finally(() => {
submitting.value = false;

View File

@ -1,12 +1,12 @@
<template>
<div class="highlight-textarea-container">
<a-textarea
<TextArea
ref="textareaWrapRef"
v-model="inputValue"
v-model:value="inputValue"
placeholder="请输入作品描述"
:disabled="disabled"
show-word-limit
:max-length="1000"
showCount
:maxlength="1000"
size="large"
class="textarea-input h-full w-full"
@input="handleInput"
@ -25,7 +25,8 @@
<script setup lang="ts">
import { Input } from 'ant-design-vue';
const {TextArea} = Input
const { TextArea } = Input;
import { ref, computed, watch, defineProps, defineEmits, onMounted, onUnmounted } from 'vue';
import { escapeRegExp } from './constants';
@ -69,8 +70,8 @@ watch(
);
// 处理输入事件
const handleInput = (value: string) => {
emit('update:modelValue', value);
const handleInput = (e) => {
emit('update:modelValue', e.currentTarget.value);
};
const escapeHtml = (str: string): string => {
@ -109,14 +110,12 @@ const generateHighlightedHtml = (): string => {
const levelStyle = props.levelMap?.get(wordInfo.risk_level);
const color = levelStyle?.color || '#F64B31';
return `<span class="text-14px font-400 lh-22px" style="color: ${color};">${escapeHtml(match)}</span>`;
return `<span class="text-14px font-400 lh-22px font-family-regular" style="color: ${color};">${escapeHtml(match)}</span>`;
});
};
onMounted(() => {
nativeTextarea =
(textareaWrapRef.value?.$el || textareaWrapRef.value)?.querySelector?.('textarea.arco-textarea') ||
document.querySelector('.textarea-input .arco-textarea');
nativeTextarea = textareaWrapRef.value?.$el?.querySelector?.('textarea.ant-input');
if (nativeTextarea) {
nativeTextarea.addEventListener('scroll', handleTextareaScroll);
@ -173,8 +172,9 @@ const handleCompositionUpdate = () => {
height: 100%;
font-size: 14px;
line-height: 22px;
font-weight: 400px;
border: 1px solid #d7d7d9;
font-weight: 400;
font-family: $font-family-regular;
//border: 1px solid #d7d7d9;
border-radius: 4px;
resize: none;
white-space: pre-wrap;
@ -192,43 +192,43 @@ const handleCompositionUpdate = () => {
background: #fff;
overflow: hidden;
@include textarea-padding;
&.focus {
border-color: rgb(var(--primary-6)) !important;
box-shadow: 0 0 0 0 var(--color-primary-light-2) !important;
}
}
:deep(.arco-textarea-wrapper) {
:deep(.ant-input-textarea) {
@include textarea-style;
.arco-textarea {
textarea {
padding: 0;
overflow: hidden;
position: absolute;
z-index: 1;
width: 100%;
background: transparent;
background-color: transparent !important;
color: transparent;
caret-color: #211f24 !important;
resize: none;
@include textarea-padding;
overflow-y: auto;
}
&.ant-input-disabled {
background: #f2f3f5 !important;
-webkit-text-fill-color: rgba(0, 0, 0, 0.25) !important;
}
.arco-textarea-word-limit {
z-index: 2;
}
&.arco-textarea-disabled {
.arco-textarea {
background: #f2f3f5;
&.ant-input-textarea-show-count {
&::after {
position: absolute;
z-index: 2;
font-size: 12px;
right: 10px;
bottom: 6px;
user-select: none;
}
}
}
// 处于中文输入法合成态时,显示真实文本并隐藏高亮层
:deep(.textarea-input.composing .arco-textarea) {
:deep(.ant-input-textarea.composing textarea) {
color: #211f24 !important;
-webkit-text-fill-color: #211f24 !important;
}