Browse Source

feat: 登录补充验证码

main
xuziqiang 5 months ago
parent
commit
09fa01122c
  1. BIN
      hx-ai-intelligent/public/asset/image/login/background.png
  2. BIN
      hx-ai-intelligent/public/asset/image/login/lg-card-bg.png
  3. 1
      hx-ai-intelligent/src/config/app.config.ts
  4. 7
      hx-ai-intelligent/src/icon/passWord.svg
  5. 7
      hx-ai-intelligent/src/icon/userName.svg
  6. 6
      hx-ai-intelligent/src/icon/verifyIcon.svg
  7. 6
      hx-ai-intelligent/src/theme/variable.less
  8. 1
      hx-op/src/config/app.config.ts
  9. 1
      lib/component/index.ts
  10. 4
      lib/component/verify/index.ts
  11. 127
      lib/component/verify/verify.vue
  12. 1
      lib/saas/store/modules/app-config.ts
  13. 5
      lib/saas/theme/variable.less
  14. 113
      lib/saas/view/system/login.vue

BIN
hx-ai-intelligent/public/asset/image/login/background.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
hx-ai-intelligent/public/asset/image/login/lg-card-bg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

1
hx-ai-intelligent/src/config/app.config.ts

@ -18,6 +18,7 @@ const transform = (data, map) => {
export const appConfig = { export const appConfig = {
projectType: 'web', projectType: 'web',
baseApi: '/api', baseApi: '/api',
projectName: '济阳站_AI智能BAS系统',
enablePermissions: true, enablePermissions: true,
// themeColor: '#eee', // themeColor: '#eee',
siderPosition: 'left', siderPosition: 'left',

7
hx-ai-intelligent/src/icon/passWord.svg

@ -1,5 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.1484375" height="19.0057373046875" viewBox="0 0 16.1484375 19.0057373046875" fill="none">
<circle cx="12" cy="12" r="12" fill-opacity="0.1"/> <path d="M13.6104 6.18792L13.3798 6.18792L13.3798 5.08296C13.3798 2.28066 10.9989 0 8.07392 0C5.14905 0 2.76692 2.28066 2.76692 5.08296L2.76692 6.18792L2.53533 6.18792C1.13511 6.19047 0 7.27711 0 8.61894L0 16.5746C0 17.9138 1.13743 19.0057 2.53533 19.0057L13.6104 19.0057C15.0071 19.0057 16.1479 17.9138 16.1479 16.5746L16.1479 8.61894C16.1479 7.27995 15.0071 6.18792 13.6104 6.18792ZM4.15229 5.08296C4.15229 3.00999 5.90854 1.32588 8.07392 1.32588C10.2371 1.32588 11.9946 3.00999 11.9946 5.08296L11.9946 6.18792L4.15229 6.18792L4.15229 5.08296ZM14.7628 16.5746C14.7628 16.8678 14.6419 17.149 14.4251 17.3558C14.2097 17.563 13.9157 17.6796 13.6104 17.6796L2.53533 17.6796C2.22998 17.6796 1.93599 17.563 1.72053 17.3558C1.50274 17.149 1.38294 16.8678 1.38294 16.5746L1.38294 8.61894C1.38294 8.00865 1.89912 7.51386 2.53533 7.51386L13.6104 7.51386C13.9157 7.51386 14.2097 7.63041 14.4251 7.83736C14.6419 8.04483 14.7628 8.32595 14.7628 8.61894L14.7628 16.5746ZM7.3809 10.8286C7.3809 10.4627 7.68973 10.1659 8.07347 10.1659C8.45617 10.1659 8.76488 10.4627 8.76488 10.8286L8.76488 14.3649C8.76488 14.7309 8.45617 15.0276 8.07347 15.0276C7.68973 15.0276 7.3809 14.7309 7.3809 14.3649L7.3809 10.8286Z" fill-rule="evenodd" fill="#7C7C7C" >
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9352 9.87544L16.531 9.87547C16.531 8.58454 16.0608 7.36797 15.2071 6.44985C14.7866 5.99763 14.2954 5.64185 13.7472 5.39246C13.1745 5.13204 12.5662 5 11.9388 5C11.3114 5 10.7031 5.13204 10.1305 5.39243C9.58225 5.64182 9.09102 5.9976 8.67051 6.44983C7.81678 7.36797 7.34659 8.58452 7.34659 9.87544H8.94234C8.94234 8.06373 10.2865 6.58981 11.9388 6.58981C13.591 6.58981 14.9352 8.06373 14.9352 9.87544ZM7.46099 9.95703H16.539V9.95705C17.5421 9.95705 18 10.7674 18 11.7659V17.1925C18 18.191 17.5421 19.0014 16.539 19.0014H7.461C6.45786 19.0014 6 18.1919 6 17.1925V11.7659C6 10.7674 6.45787 9.95703 7.46099 9.95703ZM12.908 15.1319V16.2878C12.908 16.7871 12.5012 17.1922 12.0001 17.1922C11.4992 17.1922 11.0924 16.787 11.0924 16.2878V15.1328C10.5522 14.819 10.1846 14.242 10.1846 13.5745C10.1846 12.5751 10.997 11.7656 12.0001 11.7656C13.0034 11.7656 13.8157 12.5751 13.8157 13.5745C13.8159 14.242 13.4481 14.819 12.908 15.1319Z" /> </path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

7
hx-ai-intelligent/src/icon/userName.svg

@ -1,5 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="19" height="20.010009765625" viewBox="0 0 19 20.010009765625" fill="none">
<circle cx="12" cy="12" r="12" fill-opacity="0.1"/> <path d="M13.3 5.95C13.3 3.71639 11.4895 1.91 9.26 1.91C7.02904 1.91 5.22 3.71639 5.22 5.95C5.22 8.18291 7.02904 10 9.26 10C11.4907 10 13.3 8.18291 13.3 5.95ZM18.96 18.78C18.9879 18.8668 19 18.954 19 19.05C19 19.5756 18.5742 20.01 18.05 20.01C17.7989 20.01 17.5571 19.9088 17.38 19.73C17.2004 19.5511 17.1 19.3033 17.1 19.05L17.08 19.05C16.8338 15.063 13.5383 11.9 9.5 11.9C5.46167 11.9 2.16878 15.063 1.92 19.05L1.9 19.05C1.9 19.5756 1.47526 20.01 0.95 20.01C0.700244 20.01 0.457124 19.9086 0.28 19.73C0.100434 19.5514 0 19.3035 0 19.05C0 18.9599 0.015708 18.8735 0.04 18.79C0.315269 15.1717 2.59568 12.1213 5.78 10.76C4.2988 9.67791 3.33 7.92778 3.33 5.95C3.33 2.6628 5.99205 0 9.27 0C12.5488 0 15.2 2.6628 15.2 5.95C15.2 7.78701 14.3536 9.5196 12.91 10.64C16.2461 11.9305 18.67 15.0545 18.96 18.78Z" fill="#7C7C7C" >
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3936 10.8691C12.0322 11.4221 10.4652 11.1164 9.42323 10.0946C8.56315 9.25057 8.18933 8.04123 8.42675 6.87091C8.66417 5.70058 9.48151 4.72368 10.6055 4.26681C11.4814 3.91106 12.518 3.91106 13.3939 4.26681C14.7552 4.81996 15.6428 6.12287 15.6427 7.56804C15.6427 9.01321 14.755 10.3161 13.3936 10.8691ZM9.08048 11.9575H14.9191H14.9193C16.6182 11.9575 18 13.3128 18 14.9788C18 16.6448 16.618 18 14.9193 18H9.08048C7.38197 18 6 16.6448 6 14.9788C6 13.3128 7.38176 11.9575 9.08048 11.9575Z" /> </path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 1002 B

6
hx-ai-intelligent/src/icon/verifyIcon.svg

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M10.4557 18.6706C10.4092 18.6706 10.3627 18.6654 10.3173 18.6549C10.2426 18.6377 8.46434 18.2175 6.67349 16.914C5.61102 16.1408 4.76262 15.2154 4.15213 14.1639C3.38754 12.847 3 11.3377 3 9.67612L3 4.20406C3 3.94798 3.15846 3.71857 3.39803 3.62791L10.2384 1.03976C10.3789 0.986755 10.534 0.986755 10.6745 1.03976L17.5149 3.62788C17.7545 3.7184 17.9132 3.94788 17.9131 4.20406L17.9131 9.67612C17.9131 11.3377 17.5256 12.8474 16.761 14.1639C16.1505 15.2145 15.3021 16.1408 14.2397 16.914C12.4474 18.2165 10.6691 18.6367 10.5942 18.6549C10.5488 18.6654 10.5023 18.6706 10.4557 18.6706ZM4.23244 4.6292L4.23244 9.67612C4.23244 12.2738 5.28627 14.3656 7.36478 15.8929C8.68451 16.8625 10.0281 17.2957 10.4557 17.4171C10.8834 17.2957 12.2273 16.8625 13.547 15.8929C15.6248 14.3656 16.6793 12.2743 16.6793 9.67612L16.6793 4.6292L10.4557 2.27499L4.23244 4.6292Z" fill="#7C7C7C" >
</path>
<path d="M9.49429 12.2693L9.48495 12.2693C9.3183 12.2668 9.15976 12.1969 9.04556 12.0755L7.07401 9.98022C6.84058 9.73248 6.8522 9.34241 7.09992 9.10895C7.34769 8.87552 7.73776 8.88712 7.97119 9.13489L9.5076 10.7691L13.1869 7.08978C13.4275 6.849 13.8177 6.849 14.0584 7.08969C14.2992 7.33041 14.2992 7.72064 14.0584 7.96133L9.9299 12.0886C9.81445 12.2043 9.65771 12.2693 9.49429 12.2693Z" fill="#7C7C7C" >
</path>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

6
hx-ai-intelligent/src/theme/variable.less

@ -1,7 +1,7 @@
// @import "./global-antd.less"; // @import "./global-antd.less";
@primary-color: #ff7602; // 全局主色 // @primary-color: #ff7602; // 全局主色
@layout-header-hover: #924908; //hover // @layout-header-hover: #924908; //hover
@layout-header-background: #111519; // 头部背景色 // @layout-header-background: #111519; // 头部背景色
// @layout-header-background:url(/asset/image/header_background.png) no-repeat; // @layout-header-background:url(/asset/image/header_background.png) no-repeat;
// @ant-layout-sider-collapsed-background:url(/asset/image/sider_collapsed_background.png) no-repeat; // @ant-layout-sider-collapsed-background:url(/asset/image/sider_collapsed_background.png) no-repeat;

1
hx-op/src/config/app.config.ts

@ -12,6 +12,7 @@ const transform = (data, map) => {
}; };
export const appConfig = { export const appConfig = {
projectType: 'web', projectType: 'web',
projectName: '济阳站_AI智能BAS系统',
baseApi: '/api', baseApi: '/api',
enablePermissions: false, enablePermissions: false,
// themeColor: '#eee', // themeColor: '#eee',

1
lib/component/index.ts

@ -67,6 +67,7 @@ export { NsVNode } from './VNode';
export { NsMessage } from './message'; export { NsMessage } from './message';
export { NsModal } from './modal'; export { NsModal } from './modal';
export { NsDrawer } from './drawer'; export { NsDrawer } from './drawer';
export { NsVerify } from './verify';
export { NsForm } from './form/form'; export { NsForm } from './form/form';
export * from './table'; export * from './table';

4
lib/component/verify/index.ts

@ -0,0 +1,4 @@
import verify from './verify.vue';
import { withInstall } from '/nerv-lib/util';
export const NsVerify = withInstall(verify);

127
lib/component/verify/verify.vue

@ -0,0 +1,127 @@
<template>
<div class="img-verify">
<canvas ref="verify" :width="state.width" :height="state.height" @click="handleDraw"></canvas>
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted, ref, toRefs, defineEmits } from 'vue';
const verify = ref(null);
const emit = defineEmits(['get-code']);
defineOptions({
name: 'NsVerify',
});
const state = reactive({
pool: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', //
width: 120,
height: 48,
imgCode: '',
});
onMounted(() => {
//
state.imgCode = draw();
emit('get-code', state.imgCode);
});
//
const handleDraw = () => {
state.imgCode = draw();
emit('get-code', state.imgCode);
};
//
const randomNum = (min, max) => {
return parseInt(Math.random() * (max - min) + min);
};
//
const randomColor = (min, max) => {
const r = randomNum(min, max);
const g = randomNum(min, max);
const b = randomNum(min, max);
return `rgb(${r},${g},${b})`;
};
//
const draw = () => {
// 3.
const ctx = verify.value?.getContext('2d');
//
ctx.fillStyle = randomColor(180, 230);
//
ctx.fillRect(0, 0, state.width, state.height);
// paramText
let imgCode = '';
// 4.
for (let i = 0; i < 4; i++) {
//
const text = state.pool[randomNum(0, state.pool.length)];
imgCode += text;
//
const fontSize = randomNum(Math.floor(state.height / 2), state.height);
//
const deg = randomNum(-30, 30);
/*
* 绘制文字并让四个文字在不同的位置显示的思路 :
* 1定义字体
* 2定义对齐方式
* 3填充不同的颜色
* 4保存当前的状态以防止以上的状态受影响
* 5平移translate()
* 6旋转 rotate()
* 7填充文字
* 8restore出栈
* */
ctx.font = fontSize + 'px Simhei';
ctx.textBaseline = 'top';
ctx.fillStyle = randomColor(80, 150);
/*
* save() 方法把当前状态的一份拷贝压入到一个保存图像状态的栈中
* 这就允许您临时地改变图像状态
* 然后通过调用 restore() 来恢复以前的值
* save是入栈restore是出栈
* 用来保存Canvas的状态save之后可以调用Canvas的平移放缩旋转错切裁剪等操作 restore用来恢复Canvas之前保存的状态防止save后对Canvas执行的操作对后续的绘制有影响
*
* */
ctx.save();
ctx.translate(30 * i + 15, 15);
ctx.rotate((deg * Math.PI) / 180);
// fillText()
// 使 font 使 fillStyle /
// fillText(text,x,y,maxWidth);
ctx.fillText(text, -15 + 5, -15);
ctx.restore();
}
// 5.5线,线
for (let i = 0; i < 5; i++) {
ctx.beginPath();
ctx.moveTo(randomNum(0, state.width), randomNum(0, state.height));
ctx.lineTo(randomNum(0, state.width), randomNum(0, state.height));
ctx.strokeStyle = randomColor(180, 230);
ctx.closePath();
ctx.stroke();
}
// 6.40
for (let i = 0; i < state.height; i++) {
ctx.beginPath();
ctx.arc(
randomNum(0, state.width),
randomNum(0, state.height),
randomNum(0, 3), //
0,
2 * Math.PI,
);
ctx.closePath();
ctx.fillStyle = randomColor(150, 200);
ctx.fill();
}
return imgCode;
};
</script>
<style scoped>
.img-verify {
height: 48px;
/* margin: 0 0.1rem; */
}
.img-verify canvas {
cursor: pointer;
}
</style>

1
lib/saas/store/modules/app-config.ts

@ -4,6 +4,7 @@ import { useApi, HttpRequestConfig } from '/nerv-lib/use/use-api';
interface AppConfig { interface AppConfig {
projectType: string; projectType: string;
projectName: string;
baseApi: string; baseApi: string;
timeout: number; timeout: number;
pagePermission: boolean; pagePermission: boolean;

5
lib/saas/theme/variable.less

@ -1,4 +1,4 @@
@primary-color: #37abc4; // 全局主色 @primary-color: #2778FF; // 全局主色
@white: #fff; @white: #fff;
@black: #000; @black: #000;
@ -25,6 +25,9 @@
// Border color // Border color
@border-color-base: hsv(0, 0, 85%); // base border outline a component @border-color-base: hsv(0, 0, 85%); // base border outline a component
// border
@border-radius-base: 4px;
//btn //btn
//@btn-disable-color: #9b9b9b; //禁用按钮color //@btn-disable-color: #9b9b9b; //禁用按钮color
//@btn-disable-bg: #f5f5f5; //禁用按钮background //@btn-disable-bg: #f5f5f5; //禁用按钮background

113
lib/saas/view/system/login.vue

@ -16,24 +16,31 @@
backgroundSize: 'cover', backgroundSize: 'cover',
}"> }">
<div class="lg_card"> <div class="lg_card">
<h1 class="lg_card_title">账号登录</h1> <h1 class="lg_card_title">{{ configStore.projectName }}</h1>
<p v-show="!errorShow" style="height: 8px"></p> <p v-show="!errorShow" style="height: 8px"></p>
<p v-show="errorShow" class="lg_card_error">{{ errorMsg }}</p> <p v-show="errorShow" class="lg_card_error">{{ errorMsg }}</p>
<p class="lg_card_tip">用户名/手机号</p> <!-- <p class="lg_card_tip">用户名/手机号</p> -->
<a-input <a-input class="loginInfo" placeholder="用户名" v-model:value="userName">
placeholder="登录账号"
v-model:value="userName"
style="height: 48px; margin-bottom: 20px">
<template #prefix> <template #prefix>
<ns-icon class="loginIcon" name="userName" size="25" style="margin-right: 5px" /> <ns-icon class="loginIcon" name="userName" size="19" style="margin-right: 20px" />
</template> </template>
</a-input> </a-input>
<p class="lg_card_tip">密码</p> <!-- <p class="lg_card_tip">密码</p> -->
<a-input-password placeholder="登录密码" v-model:value="password" style="height: 48px"> <a-input-password class="loginInfo" placeholder="密码" v-model:value="password">
<template #prefix> <template #prefix>
<ns-icon class="loginIcon" name="passWord" size="25" style="margin-right: 5px" /> <ns-icon class="loginIcon" name="passWord" size="19" style="margin-right: 20px" />
</template> </template>
</a-input-password> </a-input-password>
<!-- 验证码 -->
<a-input v-model:value="code" placeholder="验证码" class="loginInfo">
<template #prefix>
<ns-icon class="loginIcon" name="verifyIcon" size="19" style="margin-right: 20px" />
</template>
<template #addonAfter>
<ns-verify @get-code="onGetCode" />
</template>
</a-input>
<a-button <a-button
@click="submit" @click="submit"
:loading="loading" :loading="loading"
@ -55,7 +62,7 @@
import { authorizationService } from '/nerv-base/store/modules/authorization-service'; import { authorizationService } from '/nerv-base/store/modules/authorization-service';
import { Cookies } from '/nerv-lib/util/cookie'; import { Cookies } from '/nerv-lib/util/cookie';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { http } from '/nerv-lib/util';
export default defineComponent({ export default defineComponent({
name: 'UserLogin', name: 'UserLogin',
setup() { setup() {
@ -69,20 +76,37 @@
const router = useRouter(); const router = useRouter();
const title = ref<string>(''); const title = ref<string>('');
const initUrl = ref(''); const initUrl = ref('');
const verifyCode = ref('');
const code = ref();
title.value = '账号登录'; title.value = '账号登录';
// title.value = appConfig.title ? appConfig.title : ''; // title.value = appConfig.title ? appConfig.title : '';
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const configStore = appConfigStore(); const configStore = appConfigStore();
const { getThemeConfig: themeConfig } = storeToRefs(configStore); console.log(configStore);
const { getThemeConfig: themeConfig, projectName } = storeToRefs(configStore);
const useAuthorization = authorizationService(); const useAuthorization = authorizationService();
const submit = (): void => { const submit = (): void => {
if (password.value === '') { if (password.value === '') {
errorMsg.value = '请输入密码'; errorMsg.value = '请输入密码';
errorShow.value = true; errorShow.value = true;
return;
} }
if (userName.value === '') { if (userName.value === '') {
errorMsg.value = '请输入账号'; errorMsg.value = '请输入账号';
errorShow.value = true; errorShow.value = true;
return;
}
if (!code.value) {
errorMsg.value = '请输入验证码';
errorShow.value = true;
return;
}
if (code.value.toLocaleLowerCase() !== verifyCode.value.toLocaleLowerCase()) {
errorMsg.value = '请输入正确的验证码';
errorShow.value = true;
return;
} }
if (userName.value !== '' && password.value !== '') { if (userName.value !== '' && password.value !== '') {
errorShow.value = false; errorShow.value = false;
@ -159,7 +183,13 @@
? (url.value = 'src/assetimg/background.jpg') ? (url.value = 'src/assetimg/background.jpg')
: (url.value = 'src/assetimg/background.png'); : (url.value = 'src/assetimg/background.png');
}; };
const onGetCode = (res) => {
verifyCode.value = res;
};
return { return {
projectName,
onGetCode,
code,
title, title,
themeConfig, themeConfig,
url, url,
@ -173,6 +203,7 @@
checkoutLogo, checkoutLogo,
checkoutBg, checkoutBg,
errorShow, errorShow,
configStore,
}; };
}, },
created() { created() {
@ -219,12 +250,12 @@
// border-radius: 50%; // border-radius: 50%;
} }
.lg_card .loginIcon { // .lg_card .loginIcon {
color: @primary-color !important; // color: @primary-color !important;
} // }
.ant-layout-header { .ant-layout-header {
min-height: 64px; min-height: 64px;
background: #fff !important; // background: #fff !important;
} }
.ant-layout-content { .ant-layout-content {
@ -234,7 +265,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
padding-right: 100px; padding-right: 10%;
} }
.ant-layout-footer { .ant-layout-footer {
@ -250,32 +281,56 @@
} }
.lg_card { .lg_card {
width: 385px; width: 500px;
height: 409px; height: 570px;
background: #fff; background: #fff;
box-shadow: 0 2px 2px 0 rgb(0 0 0 / 5%), 0 0 18px 0 rgb(0 0 0 / 8%); box-shadow: 0 2px 2px 0 rgb(0 0 0 / 5%), 0 0 18px 0 rgb(0 0 0 / 8%);
border-radius: 6px; border-radius: 6px;
margin: 0 30px 0 30px; padding: 50px;
padding: 35px; border-radius: 10px;
// background: ;
border: 3px solid rgba(255, 255, 255, 1);
.lg_card_tip { .lg_card_tip {
color: #a7b6c9; color: #a7b6c9;
margin-bottom: 12px; margin-bottom: 12px;
} }
.loginInfo {
height: 48px;
width: 100%;
margin-bottom: 31px;
:deep(.ant-input) {
font-size: 16px !important;
}
:deep(.ant-input-group) {
height: 100%;
.ant-input-affix-wrapper {
height: 100%;
}
.ant-input-group-addon {
padding: 0;
}
}
}
} }
.lg_card .lg_card_title { .lg_card .lg_card_title {
height: 28px; text-align: center;
font-size: 20px; font-size: 32px;
font-weight: bold; font-weight: 700;
color: #172e3d; letter-spacing: 0px;
line-height: 28px; line-height: 42px;
text-align: left; color: @primary-color;
text-align: center;
vertical-align: top;
} }
.lg_card_error { .lg_card_error {
text-align: left; text-align: left;
color: #e4393c; color: #e00e12;
font-size: 14px; font-size: 16px;
} }
.lg_card .loginIcon { .lg_card .loginIcon {
color: @primary-color !important; color: @primary-color !important;

Loading…
Cancel
Save