Merge pull request 'main' (#98) from main into prod

Reviewed-on: #98
This commit is contained in:
charles 2026-04-27 11:07:18 +08:00
commit 75475b3854
4 changed files with 520 additions and 550 deletions

View File

@ -153,6 +153,10 @@ async def loginUser(ns):
if type1 == 1: if type1 == 1:
# 手机号验证码登录 # 手机号验证码登录
userreacs = await sor.R('users', {'mobile': ns.get('username')}) userreacs = await sor.R('users', {'mobile': ns.get('username')})
if not userreacs:
userreacs = await sor.R('users', {'username': ns.get('username')})
# 如果是微信扫码后绑定已有账号 # 如果是微信扫码后绑定已有账号
if ns.get('wechat_openid'): if ns.get('wechat_openid'):
if userreacs: if userreacs:

View File

@ -43,57 +43,101 @@ async def logintype(ns):
db = DBPools() db = DBPools()
async with db.sqlorContext('kboss') as sor: async with db.sqlorContext('kboss') as sor:
type = 0
if ns.get('codeid'): domain_name = ns.get('domain_name')
type += 1 if domain_name in ['www.opencomputing.cn', 'dev.opencomputing.cn', 'localhost:9527'] and ns.get('username') != '开元云(北京)科技有限公司':
if not ns.get('mobile'):
return {
'status': False,
'msg': '请输入手机号'
}
real_mobile_li = await sor.R('users', {'username': ns['username']})
if not real_mobile_li:
return {
'status': False,
'msg': '用户名有误'
}
real_mobile = real_mobile_li[0]['mobile'] if real_mobile_li else None
if not real_mobile:
return {
'status': False,
'msg': '未查询到匹配得手机号码'
}
if real_mobile and ns['mobile'] != real_mobile:
return {
'status': False,
'msg': '您的手机号与用户名不匹配'
}
if not ns.get('codeid'):
return {
'status': False,
'msg': 'codeid不存在, 请输入验证码'
}
code = await sor.R('validatecode', {'id': ns.get('codeid'), 'vcode': ns.get('vcode')}) code = await sor.R('validatecode', {'id': ns.get('codeid'), 'vcode': ns.get('vcode')})
if len(code) < 1: if len(code) < 1:
return {'status': False, 'msg': '验证码不正确'} return {'status': False, 'msg': '验证码不正确'}
if type == 1:
# 手机号登录
users = await sor.R('users', {'mobile': ns.get('username')})
else:
password = password_encode(ns['password']) password = password_encode(ns['password'])
users = await sor.R('users', {'username': ns.get('username'), 'password': password}) users = await sor.R('users', {'username': ns.get('username'), 'password': password})
if len(users) < 1: if len(users) < 1:
return {"status": False,'msg':'用户名或密码错误'} return {"status": False,'msg':'用户名或密码错误'}
elif ns.get('username') == "admin":
return {'status': True} return {'status': True}
else: else:
reseller = await sor.R('reseller', {'domain_name': ns.get('domain_name')}) type = 0
# 查到用户的所在机构 if ns.get('codeid'):
user_org = await sor.R('organization', {'id': users[0]['orgid']}) type += 1
#0代表用户为主 1代表用户为子 code = await sor.R('validatecode', {'id': ns.get('codeid'), 'vcode': ns.get('vcode')})
user_type = 0 if len(code) < 1:
# 子页面判断用户 return {'status': False, 'msg': '验证码不正确'}
if len(reseller) >= 1: if type == 1:
if reseller[0]['orgid'] == user_org[0]['parentid']: # 手机号登录
user_type += 1 users = await sor.R('users', {'mobile': ns.get('username')})
if reseller[0]['orgid'] == users[0]['orgid']:
user_type += 1
# 主页面判断用户
else: else:
resellers = await sor.R('reseller', {'orgid': user_org[0]['parentid']}) password = password_encode(ns['password'])
if len(resellers) >= 1: users = await sor.R('users', {'username': ns.get('username'), 'password': password})
user_type += 1 if len(users) < 1:
if users[0]['user_reseller'] == '1': return {"status": False,'msg':'用户名或密码错误'}
user_type += 1 elif ns.get('username') == "admin":
# 证明是主级页面 return {'status': True}
if len(reseller) < 1: else:
# 用户为主 reseller = await sor.R('reseller', {'domain_name': ns.get('domain_name')})
if user_type == 0: # 查到用户的所在机构
return {'status': True} user_org = await sor.R('organization', {'id': users[0]['orgid']})
#0代表用户为主 1代表用户为子
user_type = 0
# 子页面判断用户
if len(reseller) >= 1:
if reseller[0]['orgid'] == user_org[0]['parentid']:
user_type += 1
if reseller[0]['orgid'] == users[0]['orgid']:
user_type += 1
# 主页面判断用户
else: else:
resellers = await sor.R('reseller', {'orgid': user_org[0]['parentid']})
if len(resellers) >= 1:
user_type += 1
if users[0]['user_reseller'] == '1':
user_type += 1
# 证明是主级页面
if len(reseller) < 1:
# 用户为主
if user_type == 0:
return {'status': True}
else:
# 用户为子
return {'status': False,'msg':'用户名或密码错误'}
# 子级页面
else:
# 用户为主
if user_type == 0:
return {'status': False,'msg':'用户名或密码错误'}
# 用户为子 # 用户为子
return {'status': False,'msg':'用户名或密码错误'} else:
# 子级页面 return {'status': True}
else:
# 用户为主
if user_type == 0:
return {'status': False,'msg':'用户名或密码错误'}
# 用户为子
else:
return {'status': True}
ret = await logintype(params_kw) ret = await logintype(params_kw)
return ret return ret

View File

@ -160,8 +160,13 @@
</div> </div>
</div> </div>
<div v-show="aiDialogVisible" class="ai-chat-panel"> <div
<div class="ai-chat-panel__header"> v-show="aiDialogVisible"
ref="aiChatPanel"
class="ai-chat-panel"
:style="aiPanelStyle"
>
<div class="ai-chat-panel__header" @mousedown="startAIPanelDrag">
<div class="ai-chat-panel__title"> <div class="ai-chat-panel__title">
<span class="ai-chat-panel__logo">AI</span> <span class="ai-chat-panel__logo">AI</span>
<span>有问题找开元</span> <span>有问题找开元</span>
@ -260,6 +265,15 @@ export default Vue.extend({
aiInput: '', aiInput: '',
aiLoading: false, aiLoading: false,
aiMessages: [], aiMessages: [],
aiPanelPosition: {
left: null,
top: null
},
aiPanelDragging: false,
aiPanelDragOffset: {
x: 0,
y: 0
},
aiQuickQuestions: [ aiQuickQuestions: [
'推荐适合训练大模型的 GPU 服务器', '推荐适合训练大模型的 GPU 服务器',
'4090 和 A100 怎么选', '4090 和 A100 怎么选',
@ -308,6 +322,9 @@ export default Vue.extend({
} }
}); });
}, },
beforeDestroy() {
this.stopAIPanelDrag()
},
computed: { computed: {
...mapGetters(["sidebar", "avatar", "device"]), ...mapGetters(["sidebar", "avatar", "device"]),
...mapState({ ...mapState({
@ -342,6 +359,18 @@ export default Vue.extend({
} else { } else {
return this.$route.path.includes('/homePage/index'); return this.$route.path.includes('/homePage/index');
} }
},
aiPanelStyle() {
if (this.aiPanelPosition.left === null || this.aiPanelPosition.top === null) {
return {}
}
return {
left: `${this.aiPanelPosition.left}px`,
top: `${this.aiPanelPosition.top}px`,
right: 'auto',
bottom: 'auto'
}
} }
}, },
methods: { methods: {
@ -350,12 +379,14 @@ export default Vue.extend({
handleAIClick() { handleAIClick() {
this.aiDialogVisible = true this.aiDialogVisible = true
this.$nextTick(() => { this.$nextTick(() => {
this.initAIPanelPosition()
this.scrollAIChatToBottom() this.scrollAIChatToBottom()
}) })
}, },
closeAIPanel() { closeAIPanel() {
this.aiDialogVisible = false this.aiDialogVisible = false
this.stopAIPanelDrag()
}, },
resetAIChat() { resetAIChat() {
@ -367,6 +398,73 @@ export default Vue.extend({
this.sendAIMessage(question) this.sendAIMessage(question)
}, },
initAIPanelPosition() {
if (this.aiPanelPosition.left !== null && this.aiPanelPosition.top !== null) {
return
}
const panel = this.$refs.aiChatPanel
if (!panel) return
const panelWidth = panel.offsetWidth || 380
const panelHeight = panel.offsetHeight || 620
this.aiPanelPosition = {
left: Math.max(window.innerWidth - panelWidth - 24, 16),
top: Math.max(window.innerHeight - panelHeight - 24, 16)
}
},
startAIPanelDrag(event) {
if (event.button !== 0) return
if (event.target && event.target.closest('.ai-chat-panel__close')) {
return
}
const panel = this.$refs.aiChatPanel
if (!panel) return
const rect = panel.getBoundingClientRect()
this.aiPanelDragging = true
this.aiPanelDragOffset = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
}
window.addEventListener('mousemove', this.handleAIPanelDrag)
window.addEventListener('mouseup', this.stopAIPanelDrag)
document.body.style.userSelect = 'none'
},
handleAIPanelDrag(event) {
if (!this.aiPanelDragging) return
const panel = this.$refs.aiChatPanel
if (!panel) return
const panelWidth = panel.offsetWidth || 380
const panelHeight = panel.offsetHeight || 620
const minLeft = 0
const minTop = 0
const maxLeft = Math.max(window.innerWidth - panelWidth, 0)
const maxTop = Math.max(window.innerHeight - panelHeight, 0)
const nextLeft = event.clientX - this.aiPanelDragOffset.x
const nextTop = event.clientY - this.aiPanelDragOffset.y
this.aiPanelPosition = {
left: Math.min(Math.max(nextLeft, minLeft), maxLeft),
top: Math.min(Math.max(nextTop, minTop), maxTop)
}
},
stopAIPanelDrag() {
this.aiPanelDragging = false
window.removeEventListener('mousemove', this.handleAIPanelDrag)
window.removeEventListener('mouseup', this.stopAIPanelDrag)
document.body.style.userSelect = ''
},
handleAIKeydown(event) { handleAIKeydown(event) {
if (event.key === 'Enter' && !event.shiftKey) { if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault() event.preventDefault()
@ -1251,6 +1349,7 @@ export default Vue.extend({
justify-content: space-between; justify-content: space-between;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
background: #fff; background: #fff;
cursor: move;
} }
&__title { &__title {

File diff suppressed because it is too large Load Diff