更新
This commit is contained in:
parent
8146b165f9
commit
e7effb0881
@ -5,6 +5,14 @@
|
||||
|
||||
# CHANGE
|
||||
|
||||
## 1.0.6
|
||||
|
||||
2023年06月26日
|
||||
|
||||
### 添加
|
||||
|
||||
- 物品功能标签
|
||||
|
||||
## 1.0.5
|
||||
|
||||
2023年06月19日
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wuhu-torn-helper",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"description": "芜湖助手",
|
||||
"scripts": {
|
||||
"release": "cross-env NODE_ENV=production rollup -c && node build.mjs",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -13,6 +13,7 @@ import TravelItem, { TravelItemKey } from "./action/TravelItem";
|
||||
import QuickGymTrain, { QuickGymTrainKey } from "./action/QuickGymTrain";
|
||||
import QuickFlyBtnHandler, { QuickFlyBtnHandlerKey } from "./handler/QuickFlyBtnHandler";
|
||||
import ItemHelper, { ItemHelperKey } from "./utils/ItemHelper";
|
||||
import MathUtils, { MathUtilsKey } from "./utils/MathUtils";
|
||||
|
||||
@ClassName("IconHelper")
|
||||
@Injectable()
|
||||
@ -31,6 +32,7 @@ export default class IconHelper {
|
||||
private readonly quickGymTrain: QuickGymTrain,
|
||||
private readonly quickFlyBtnHandler: QuickFlyBtnHandler,
|
||||
private readonly itemHelper: ItemHelper,
|
||||
private readonly mathUtils: MathUtils,
|
||||
) {
|
||||
this._element = document.createElement('div');
|
||||
}
|
||||
@ -47,6 +49,7 @@ export default class IconHelper {
|
||||
app.config.warnHandler = (err) => this.logger.warn('[VUE警告]', err);
|
||||
app.provide(LoggerKey, this.logger);
|
||||
app.provide(CommonUtilsKey, this.commonUtils);
|
||||
app.provide(MathUtilsKey, this.mathUtils);
|
||||
app.provide(TravelItemKey, this.travelItem);
|
||||
app.provide(PopupWrapperKey, this.popupWrapper);
|
||||
app.provide(LocalConfigWrapperKey, this.localConfigWrapper);
|
||||
|
||||
@ -374,8 +374,7 @@ export default class TranslateNew extends Provider implements ResponseInject {
|
||||
name.innerText = `${ name.innerText } ${ nameZh }`;
|
||||
}
|
||||
// 操作按钮
|
||||
let actions = elem.querySelectorAll('.icon-h');
|
||||
actions.forEach(action => {
|
||||
elem.querySelectorAll('.icon-h').forEach(action => {
|
||||
let attrTitle = action.getAttribute('title');
|
||||
// TODO
|
||||
let zh = itemPageDict[attrTitle];
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import ClassName from "../../container/ClassName";
|
||||
import { Injectable } from "../../container/Injectable";
|
||||
import { InjectionKey } from "vue";
|
||||
|
||||
@ClassName('MathUtils')
|
||||
@Injectable()
|
||||
@ -14,3 +15,5 @@ export default class MathUtils {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
}
|
||||
|
||||
export const MathUtilsKey = Symbol('MathUtilsKey') as InjectionKey<MathUtils>
|
||||
|
||||
5
src/ts/func/utils/Sleep.ts
Normal file
5
src/ts/func/utils/Sleep.ts
Normal file
@ -0,0 +1,5 @@
|
||||
const Sleep = (ms: number = 0) => {
|
||||
return new Promise(resolve => window.setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
export default Sleep
|
||||
21
src/ts/func/utils/equipItem.ts
Normal file
21
src/ts/func/utils/equipItem.ts
Normal file
@ -0,0 +1,21 @@
|
||||
const equipItem = (itemId: string, armoryId: string, type: number) => {
|
||||
return fetch(window.addRFC("https://www.torn.com/item.php"), {
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin",
|
||||
"x-requested-with": "XMLHttpRequest"
|
||||
},
|
||||
"referrer": "https://www.torn.com/item.php",
|
||||
"referrerPolicy": "strict-origin-when-cross-origin",
|
||||
"body": `step=actionForm&item_id=${ itemId }&armour-from-set=&type=${ type }&action=equip&item=${ itemId }&id=${ armoryId }&confirm=1`,
|
||||
"method": "POST",
|
||||
"mode": "cors",
|
||||
"credentials": "include"
|
||||
})
|
||||
}
|
||||
|
||||
export default equipItem
|
||||
@ -15,9 +15,9 @@
|
||||
</el-badge>
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<el-dialog v-model="drawer" :destroy-on-close="false" :fullscreen="isMobilePhone" :lock-scroll="true"
|
||||
:title="editableTabsValue" width="65%">
|
||||
<el-tabs v-model="editableTabsValue" closable type="card" @tab-remove="removeTab">
|
||||
<el-dialog v-model="drawer" :fullscreen="isMobilePhone" :lock-scroll="true"
|
||||
width="65%">
|
||||
<el-tabs v-model="editableTabsValue" closable style="margin-top: -1em" type="border-card" @tab-remove="removeTab">
|
||||
<el-tab-pane v-for="item in editableTabs" :key="item.name" :label="item.title" :name="item.name">
|
||||
<component :is="item.content"/>
|
||||
</el-tab-pane>
|
||||
@ -82,20 +82,20 @@
|
||||
<el-menu-item @click="quickGymTrain.doTrain(BATTLE_STAT.DEX)">闪避
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-for="(item, i) in menuItemList" :index="(3 + i).toString()" @click="menuClick(item)">
|
||||
<el-icon>{{ item.title.slice(0, 2) }}</el-icon>
|
||||
<span>{{ item.title.slice(2, item.title.length) }}</span>
|
||||
</el-menu-item>
|
||||
<el-sub-menu :index="2 + menuItemList.length + 1 + ''">
|
||||
<el-sub-menu index="3">
|
||||
<template #title>
|
||||
<el-icon>🪓</el-icon>
|
||||
<span>老功能</span>
|
||||
</template>
|
||||
<el-menu-item :index="(2 + menuItemList.length + 1) + '-' + 1" @click="_adHelper">📜️ 传单助手</el-menu-item>
|
||||
<el-menu-item :index="(2 + menuItemList.length + 1) + '-' + 2" @click="_safeKeeper">🛡️ 守望者</el-menu-item>
|
||||
<el-menu-item :index="(2 + menuItemList.length + 1) + '-' + 3" @click="MUZHUANG">🌲 寻找木桩</el-menu-item>
|
||||
<el-menu-item :index="(2 + menuItemList.length + 1) + '-' + 4" @click="_setting">⚙️ 助手设置</el-menu-item>
|
||||
<el-menu-item index="3-1" @click="_adHelper">📜️ 传单助手</el-menu-item>
|
||||
<el-menu-item index="3-2" @click="_safeKeeper">🛡️ 守望者</el-menu-item>
|
||||
<el-menu-item index="3-3" @click="MUZHUANG">🌲 寻找木桩</el-menu-item>
|
||||
<el-menu-item index="3-4" @click="_setting">⚙️ 助手设置</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-for="(item, i) in menuItemList" :index="(4 + i).toString()" @click="menuClick(item)">
|
||||
<el-icon>{{ item.title.slice(0, 2) }}</el-icon>
|
||||
<span>{{ item.title.slice(2, item.title.length) }}</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-drawer>
|
||||
</el-config-provider>
|
||||
@ -127,6 +127,7 @@ import PTMarketView from "./PTMarketView.vue";
|
||||
import QuickCrime from "./QuickCrime.vue";
|
||||
import UpdateDate from "./UpdateScript.vue";
|
||||
import VirusProgramming from "./VirusProgramming.vue";
|
||||
import InventoryView from "./InventoryView.vue";
|
||||
|
||||
const logger = inject(LoggerKey);
|
||||
const quickGymTrain = inject(QuickGymTrainKey);
|
||||
@ -208,6 +209,10 @@ const menuItemList: MenuItem[] = [
|
||||
title: '🫵 关闭店铺(双击开启)',
|
||||
handler: () => bazaarControl.method(),
|
||||
},
|
||||
{
|
||||
title: '📦 物品',
|
||||
template: InventoryView,
|
||||
},
|
||||
{
|
||||
title: '🚀 更新历史',
|
||||
handler: () => {
|
||||
|
||||
300
src/vue/InventoryView.vue
Normal file
300
src/vue/InventoryView.vue
Normal file
@ -0,0 +1,300 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import { Check, Delete, Promotion } from "@element-plus/icons-vue"
|
||||
import { ElMessage } from "element-plus"
|
||||
import { inject, onMounted, ref } from "vue"
|
||||
import { LoggerKey } from "../ts/class/Logger"
|
||||
import { itemNameDict } from "../ts/dictionary/translation"
|
||||
import Sleep from "../ts/func/utils/Sleep"
|
||||
import equipItem from "../ts/func/utils/equipItem"
|
||||
import useItem from "../ts/func/utils/useItem"
|
||||
|
||||
const logger = inject(LoggerKey)
|
||||
|
||||
type Item = {
|
||||
id: number
|
||||
name: string
|
||||
nameZh: string
|
||||
amount: number
|
||||
isEquipped: boolean
|
||||
details: ItemDetails
|
||||
isDetailsLoading: boolean
|
||||
armoryId: number
|
||||
damage: number
|
||||
accuracy: number
|
||||
defence: number
|
||||
type: number
|
||||
}
|
||||
|
||||
type ItemDetails = {
|
||||
itemType: string
|
||||
itemCost: string
|
||||
itemValue: string
|
||||
itemRareTitle: string
|
||||
extras: { icon: string; cl: string; type: string; title: string; value: string }[]
|
||||
itemInfo: string
|
||||
itemSell: string
|
||||
itemID: number
|
||||
itemName: string
|
||||
itemInfoContent: string
|
||||
itemCirculation: string
|
||||
armoryID: boolean
|
||||
itemRareIcon: string
|
||||
}
|
||||
|
||||
const itemList = ref<Item[]>([])
|
||||
const disabledInfiniteLoading = ref(false)
|
||||
const categorySelected = ref('Drug')
|
||||
const categories = [
|
||||
{ label: '📦 ', value: '' },
|
||||
{ label: '主', value: 'Primary' },
|
||||
{ label: '副', value: 'Secondary' },
|
||||
{ label: '🔪 ', value: 'Melee' },
|
||||
{ label: '💣 ', value: 'Temporary' },
|
||||
{ label: '🛡 ', value: 'Defensive' },
|
||||
{ label: '👔 ', value: 'Clothing' },
|
||||
{ label: '🏥 ', value: 'Medical' },
|
||||
{ label: '💊 ', value: 'Drug' },
|
||||
{ label: '🥤 ', value: 'Energy+Drink' },
|
||||
{ label: '🍺 ', value: 'Alcohol' },
|
||||
{ label: '🍬 ', value: 'Candy' },
|
||||
{ label: '⬆️ ', value: 'Booster' },
|
||||
{ label: '🪄 ', value: 'Enhancer' },
|
||||
{ label: '📦 ', value: 'Supply+Pack' },
|
||||
{ label: '🔌 ', value: 'Electronic' },
|
||||
{ label: '💎 ', value: 'Jewelry' },
|
||||
{ label: '🌹 ', value: 'Flower' },
|
||||
{ label: '🧸 ', value: 'Plushie' },
|
||||
{ label: '🚗 ', value: 'Car' },
|
||||
{ label: '🦠 ', value: 'Virus' },
|
||||
{ label: '📖 ', value: 'Book' },
|
||||
{ label: '🌟', value: 'Special' },
|
||||
{ label: '🛍 ', value: 'Other' },
|
||||
// TODO
|
||||
{ label: '🗼 ', value: '' },
|
||||
{ label: '🏆 ', value: '' },
|
||||
]
|
||||
|
||||
const fetchList = async (start: number = 0): Promise<string> => {
|
||||
let json: { html?: string, error?: string, text?: string }
|
||||
try {
|
||||
json = await (await fetch(window.addRFC("https://www.torn.com/item.php"), {
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin",
|
||||
"x-requested-with": "XMLHttpRequest"
|
||||
},
|
||||
"referrer": "https://www.torn.com/item.php",
|
||||
"referrerPolicy": "strict-origin-when-cross-origin",
|
||||
"body": categorySelected.value ? `step=getCategoryList&itemName=${ categorySelected.value }&start=${ start }&test=true&prevtotal=0` : `step=getNotAllItemsListWithoutGroups&start=${ start }&queue=All`,
|
||||
"method": "POST",
|
||||
"mode": "cors",
|
||||
"credentials": "include"
|
||||
})).json()
|
||||
} catch (e) {
|
||||
logger.error(e.stack)
|
||||
ElMessage.error('物品获取失败 ' + e.message)
|
||||
throw e
|
||||
}
|
||||
if (json.html) return json.html
|
||||
else {
|
||||
const err = json.error || json.text || '获取物品列表时没有内容'
|
||||
logger.error(err)
|
||||
ElMessage.error('获取物品失败 ' + err)
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchItemDetails = async (id: number): Promise<ItemDetails> => {
|
||||
let ret: ItemDetails = null
|
||||
try {
|
||||
ret = await (await fetch(window.addRFC("https://www.torn.com/page.php?sid=inventory"), {
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin",
|
||||
"x-requested-with": "XMLHttpRequest"
|
||||
},
|
||||
"referrer": "https://www.torn.com/item.php",
|
||||
"referrerPolicy": "strict-origin-when-cross-origin",
|
||||
"body": "itemID=" + id,
|
||||
"method": "POST",
|
||||
"mode": "cors",
|
||||
"credentials": "include"
|
||||
})).json()
|
||||
} catch (e) {
|
||||
logger.error(e.stack)
|
||||
ElMessage.error('物品详情获取失败 ' + e.message)
|
||||
throw e
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
const parseListHtml = (html: string) => {
|
||||
const ret: Item[] = []
|
||||
let tmp = document.createElement('div')
|
||||
tmp.innerHTML = html
|
||||
tmp.childNodes.forEach(li => {
|
||||
if (li.nodeType === 1) {
|
||||
const item: Item = {
|
||||
type: 0,
|
||||
defence: 0,
|
||||
accuracy: 0, damage: 0,
|
||||
armoryId: 0,
|
||||
details: undefined,
|
||||
isDetailsLoading: false,
|
||||
amount: -1, isEquipped: false, id: -1, name: "", nameZh: ""
|
||||
}
|
||||
let elem = li as HTMLElement
|
||||
// 物品名
|
||||
let name: HTMLElement = elem.querySelector('.name-wrap .name')
|
||||
item.name = name.innerText.trim()
|
||||
item.nameZh = itemNameDict[item.name]
|
||||
// 堆叠数量
|
||||
item.amount = Number(elem.getAttribute('data-qty'))
|
||||
// id
|
||||
item.id = Number(elem.getAttribute('data-item'))
|
||||
// 装备
|
||||
item.isEquipped = elem.getAttribute('data-equipped').trim() === 'true'
|
||||
// 装备id
|
||||
item.armoryId = Number(elem.getAttribute('data-armoryid'))
|
||||
// 武器攻击
|
||||
item.damage = Number(elem.querySelector('.bonus-attachment-item-damage-bonus')?.nextElementSibling.innerText?.trim())
|
||||
// 武器命中
|
||||
item.accuracy = Number(elem.querySelector('.bonus-attachment-item-accuracy-bonus')?.nextElementSibling.innerText?.trim())
|
||||
// 武器命中
|
||||
item.defence = Number(elem.querySelector('.bonus-attachment-item-defence-bonus')?.nextElementSibling.innerText?.trim())
|
||||
// 装备类型
|
||||
item.type = Number(elem.querySelector('ul.actions-wrap li[data-type]')?.getAttribute('data-type').trim())
|
||||
|
||||
ret.push(item)
|
||||
}
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
let mouseOverItemId: number
|
||||
const itemHover = async (item: Item) => {
|
||||
const thisId = item.id
|
||||
mouseOverItemId = item.id
|
||||
await Sleep(300)
|
||||
if (thisId === mouseOverItemId && !item.isDetailsLoading && !item.details) {
|
||||
item.isDetailsLoading = true
|
||||
try {
|
||||
item.details = await fetchItemDetails(item.id)
|
||||
} catch (e) {
|
||||
}
|
||||
item.isDetailsLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadMore = async () => {
|
||||
if (disabledInfiniteLoading.value) return
|
||||
disabledInfiniteLoading.value = true
|
||||
let newItems: Item[]
|
||||
try {
|
||||
newItems = parseListHtml(await fetchList(itemList.value.length))
|
||||
} catch (e) {
|
||||
if (e.message !== '获取物品列表时没有内容') {
|
||||
ElMessage.error(e.message)
|
||||
logger.error(e.stack)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
newItems.forEach(item => itemList.value.push(item))
|
||||
window.setTimeout(() => disabledInfiniteLoading.value = newItems.length < 1, 500)
|
||||
}
|
||||
const loadCategory = async (type: string) => {
|
||||
disabledInfiniteLoading.value = true
|
||||
categorySelected.value = type
|
||||
itemList.value = []
|
||||
itemList.value = parseListHtml(await fetchList(itemList.value.length))
|
||||
window.setTimeout(() => disabledInfiniteLoading.value = false, 500)
|
||||
}
|
||||
|
||||
const _equipItem = async (item: Item) => {
|
||||
await equipItem(String(item.id), String(item.armoryId), item.type)
|
||||
item.isEquipped = !item.isEquipped
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
itemList.value = parseListHtml(await fetchList())
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-button-group>
|
||||
<el-button v-for="item in categories" :disabled="item.value === categorySelected" @click="loadCategory(item.value)">
|
||||
{{ item.label }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<el-space size="default" wrap>
|
||||
<div v-for="item in itemList" v-infinite-scroll="loadMore" :infinite-scroll-delay="500"
|
||||
:infinite-scroll-disabled="disabledInfiniteLoading">
|
||||
<el-badge :hidden="item.amount < 2" :value="item.amount">
|
||||
<el-popover :show-after="300" :width="240" trigger="hover">
|
||||
<template #reference>
|
||||
<el-card :body-style="{ padding: '0px' }" :style="item.isEquipped ? { borderColor: '#32CD32' } : null"
|
||||
shadow="hover">
|
||||
<el-image :alt="item.id.toString()" :mouseover="itemHover(item)"
|
||||
:src="`/images/items/${item.id}/medium.png`" style="width: 60px;height: 30px"/>
|
||||
<el-row v-if="item.damage > 0 && item.accuracy > 0">
|
||||
<el-col :span="12">
|
||||
<el-tag>{{ item.damage }}</el-tag>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-tag>{{ item.accuracy }}</el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="item.defence > 0">
|
||||
<el-col :span="24">
|
||||
<el-tag>{{ item.defence }}</el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-text v-if="item.nameZh" size="large">{{ item.nameZh }}</el-text>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-text>{{ item.name }}<span v-if="item.armoryId"> - {{ item.armoryId }}</span></el-text>
|
||||
</el-row>
|
||||
<el-row v-if="item.damage">
|
||||
<el-text>攻击: {{ item.damage }}</el-text>
|
||||
</el-row>
|
||||
<el-row v-if="item.accuracy">
|
||||
<el-text>命中: {{ item.accuracy }}</el-text>
|
||||
</el-row>
|
||||
<el-row v-if="item.defence">
|
||||
<el-text>防御: {{ item.defence }}</el-text>
|
||||
</el-row>
|
||||
<el-row :gutter="4">
|
||||
<el-col :span="8">
|
||||
<el-button :icon="Check" @click="item.armoryId ? _equipItem(item) : useItem(item.id.toString())"/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button :icon="Delete" disabled/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button :icon="Promotion" disabled/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div v-if="item.details || item.isDetailsLoading" v-loading="item.isDetailsLoading"
|
||||
v-html="item.details?.itemInfoContent || ''"></div>
|
||||
<el-row v-if="item.details">
|
||||
<el-text>估值: {{ item.details.itemValue }}</el-text>
|
||||
</el-row>
|
||||
</el-popover>
|
||||
</el-badge>
|
||||
</div>
|
||||
</el-space>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@ -65,9 +65,12 @@ import { LoggerKey } from "../ts/class/Logger";
|
||||
import { ItemHelperKey } from "../ts/class/utils/ItemHelper";
|
||||
import { itemNameDict } from "../ts/dictionary/translation";
|
||||
import toThousands from "../ts/func/utils/toThousands";
|
||||
import Sleep from "../ts/func/utils/Sleep";
|
||||
import { MathUtilsKey } from "../ts/class/utils/MathUtils";
|
||||
|
||||
const logger = inject(LoggerKey);
|
||||
const itemHelper = inject(ItemHelperKey);
|
||||
const mathUtils = inject(MathUtilsKey)
|
||||
|
||||
let itemName2IdMap: { [p: string]: number } = null;
|
||||
const itemName = ref<string>('');
|
||||
@ -205,7 +208,7 @@ const buy = async (itemInfo: ItemInfo) => {
|
||||
throw new Error('bazaarId为空')
|
||||
})();
|
||||
}
|
||||
|
||||
await Sleep(mathUtils.getRandomInt(100, 110))
|
||||
// 从baz买货
|
||||
let buyResponse = await (await fetch("https://www.torn.com/bazaar.php?sid=bazaarData&step=buyItem", {
|
||||
"headers": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user