lodash#uniqWith TypeScript Examples
The following examples show how to use
lodash#uniqWith.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: contractWrappers.ts From webapp with MIT License | 6 votes |
getHistoricBalances = async (
positions: ProtectedLiquidity[],
blockNow: number,
pools: MinimalPool[]
): Promise<PoolHistoricBalance[][]> => {
const timeScales: TimeScale[] = (
[
[1, "day"],
[7, "week"]
] as [number, string][]
).map(([days, label]) => ({
blockHeight: rewindBlocksByDays(blockNow, days),
days,
label
}));
const uniqueAnchors = uniqWith(
positions.map(pos => pos.poolToken),
compareString
);
const relevantPools = pools.filter(pool =>
uniqueAnchors.some(anchor => compareString(pool.anchorAddress, anchor))
);
// @ts-ignore
return fetchHistoricBalances(timeScales, relevantPools);
}
Example #2
Source File: observables.ts From webapp with MIT License | 6 votes |
uniquePoolReserves = (
positions: ProtectedLiquidity[]
): { poolToken: string; reserveToken: string }[] =>
uniqWith(
positions,
(a, b) =>
compareString(a.poolToken, b.poolToken) &&
compareString(a.reserveToken, b.reserveToken)
).map(position => ({
reserveToken: position.reserveToken,
poolToken: position.poolToken
}))
Example #3
Source File: eosBancor.ts From webapp with MIT License | 6 votes |
@action async onAuthChange(currentUser: string | false) {
if (currentUser) {
Sentry.setUser({ id: currentUser });
const reserves = uniqWith(
this.relaysList.flatMap(relay => relay.reserves),
(a, b) => compareString(a.id, b.id)
);
this.fetchTokenBalancesIfPossible(reserves);
} else {
Sentry.configureScope(scope => scope.setUser(null));
}
}
Example #4
Source File: eosBancor.ts From webapp with MIT License | 6 votes |
@action async fetchTokenBalancesIfPossible(tokens: TokenBalanceParam[]) {
if (!this.currentUser) return;
const tokensFetched = this.currentUserBalances;
const allTokens = _.uniqWith(
this.relaysList.flatMap(relay => relay.reserves),
(a, b) => compareString(a.id, b.id)
);
const tokenBalancesNotYetFetched = _.differenceWith(
allTokens,
tokensFetched,
compareAgnosticToBalanceParam
);
const tokensToAskFor = _.uniqWith(
[
...tokens,
...tokenBalancesNotYetFetched.map(agnosticToTokenBalanceParam)
],
compareToken
);
return vxm.eosNetwork.getBalances({ tokens: tokensToAskFor, slow: false });
}
Example #5
Source File: eosBancor.ts From webapp with MIT License | 6 votes |
@action async fetchBalancesFromReserves(relays: DryRelay[]) {
const tokens = relays
.flatMap(relay => relay.reserves)
.map(reserve => ({
contract: reserve.contract,
symbol: reserve.symbol.code().to_string()
}));
const uniqueTokens = _.uniqWith(
tokens,
(a, b) =>
compareString(a.symbol, b.symbol) &&
compareString(a.contract, b.contract)
);
return vxm.eosNetwork.getBalances({
tokens: uniqueTokens,
slow: false
});
}
Example #6
Source File: explorerService.ts From nautilus-wallet with MIT License | 6 votes |
public async getTokenRates(): Promise<AssetPriceRate> {
const fromDate = new Date();
fromDate.setDate(fromDate.getDate() - 30);
const { data } = await axios.get<ErgoDexPool[]>(`https://api.ergodex.io/v1/amm/markets`, {
params: {
from: this.getUtcTimestamp(fromDate),
to: this.getUtcTimestamp(new Date())
}
});
const filtered = uniqWith(
data.filter((x) => x.baseId === ERG_TOKEN_ID),
(a, b) =>
a.quoteId === b.quoteId && new BigNumber(a.baseVolume.value).isLessThan(b.baseVolume.value)
);
return asDict(
filtered.map((r) => {
return {
[r.quoteId]: {
erg: new BigNumber(1).dividedBy(r.lastPrice).toNumber()
}
};
})
);
}
Example #7
Source File: observables.ts From webapp with MIT License | 5 votes |
getTokenMeta = async (currentNetwork: EthNetworks) => {
const networkVars = getNetworkVariables(currentNetwork);
if (currentNetwork == EthNetworks.Ropsten) {
return [
{
symbol: "BNT",
contract: networkVars.bntToken,
precision: 18
},
{
symbol: "DAI",
contract: "0xc2118d4d90b274016cb7a54c03ef52e6c537d957",
precision: 18
},
{
symbol: "WBTC",
contract: "0xbde8bb00a7ef67007a96945b3a3621177b615c44",
precision: 8
},
{
symbol: "BAT",
contract: "0x443fd8d5766169416ae42b8e050fe9422f628419",
precision: 18
},
{
symbol: "LINK",
contract: "0x20fe562d797a42dcb3399062ae9546cd06f63280",
precision: 18
},
{
contract: "0x4F5e60A76530ac44e0A318cbc9760A2587c34Da6",
symbol: "YYYY"
},
{
contract: "0x63B75DfA4E87d3B949e876dF2Cd2e656Ec963466",
symbol: "YYY"
},
{
contract: "0xAa2A908Ca3E38ECEfdbf8a14A3bbE7F2cA2a1BE4",
symbol: "XXX"
},
{
contract: "0xe4158797A5D87FB3080846e019b9Efc4353F58cC",
symbol: "XXX"
}
].map(
(x): TokenMeta => ({
...x,
id: x.contract,
image: defaultImage,
name: x.symbol
})
);
}
if (currentNetwork !== EthNetworks.Mainnet)
throw new Error("Ropsten and Mainnet supported only.");
const res: AxiosResponse<TokenMeta[]> = await axios.get(
tokenMetaDataEndpoint
);
const drafted = res.data
.filter(({ symbol, contract, image }) =>
[symbol, contract, image].every(Boolean)
)
.map(x => ({ ...x, id: x.contract }));
const existingEth = drafted.find(x => compareString(x.symbol, "eth"))!;
const withoutEth = drafted.filter(meta => !compareString(meta.symbol, "eth"));
const addedEth = {
...existingEth,
id: ethReserveAddress,
contract: ethReserveAddress
};
const final = [addedEth, existingEth, ...withoutEth];
return uniqWith(final, (a, b) => compareString(a.id, b.id));
}
Example #8
Source File: eosBancor.ts From webapp with MIT License | 5 votes |
@action async init(param?: ModuleParam) {
this.pullEvents();
if (this.initialised) return this.refresh();
try {
const [usdPriceOfBnt, v2Relays, tokenMeta] = await Promise.all([
vxm.bancor.fetchUsdPriceOfBnt(),
fetchMultiRelays(),
getTokenMeta()
]);
this.setTokenMeta(tokenMeta);
this.setBntPrice(usdPriceOfBnt);
const v1Relays = getHardCodedRelays();
const allDry = [...v1Relays, ...v2Relays.map(multiToDry)].filter(
noBlackListedReservesDry(blackListedTokens)
);
this.fetchTokenBalancesIfPossible(
_.uniqWith(
allDry.flatMap(x =>
x.reserves.map(x => ({ ...x, symbol: x.symbol.code().to_string() }))
),
compareToken
)
);
const quickTrade =
param &&
param.tradeQuery &&
param.tradeQuery.base &&
param.tradeQuery.quote;
if (quickTrade) {
const { base: fromId, quote: toId } = param!.tradeQuery!;
await this.bareMinimumForTrade({
fromId,
toId,
v1Relays,
v2Relays,
tokenMeta
});
} else {
await this.addPools({
multiRelays: v2Relays,
dryDelays: v1Relays,
tokenMeta
});
}
this.setInitialised(true);
this.setLoadingPools(false);
} catch (e) {
throw new Error(`Threw inside eosBancor: ${e.message}`);
}
}
Example #9
Source File: eosBancor.ts From webapp with MIT License | 5 votes |
@mutation updateRelayFeed(feeds: RelayFeed[]) {
this.relayFeed = _.uniqWith(
[...feeds, ...this.relayFeed],
(a, b) =>
compareString(a.smartTokenId, b.smartTokenId) &&
compareString(a.tokenId, b.tokenId)
);
}
Example #10
Source File: eosBancor.ts From webapp with MIT License | 5 votes |
@mutation updateMultiRelays(relays: EosMultiRelay[]) {
const meshedRelays = _.uniqWith(
[...relays, ...this.relaysList],
compareEosMultiRelay
);
this.relaysList = meshedRelays;
}
Example #11
Source File: loadAccounts.ts From metaplex with Apache License 2.0 | 5 votes |
loadAccounts = async (connection: Connection) => {
const state: MetaState = getEmptyMetaState();
const updateState = makeSetter(state);
const forEachAccount = processingAccounts(updateState);
const forEach =
(fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
for (const account of accounts) {
await fn(account, updateState);
}
};
const loadVaults = () =>
getProgramAccounts(connection, VAULT_ID).then(
forEachAccount(processVaultData),
);
const loadAuctions = () =>
getProgramAccounts(connection, AUCTION_ID).then(
forEachAccount(processAuctions),
);
const loadMetaplex = () =>
getProgramAccounts(connection, METAPLEX_ID).then(
forEachAccount(processMetaplexAccounts),
);
const loadCreators = () =>
getProgramAccounts(connection, METAPLEX_ID, {
filters: [
{
dataSize: MAX_WHITELISTED_CREATOR_SIZE,
},
],
}).then(forEach(processMetaplexAccounts));
const loadMetadata = () =>
pullMetadataByCreators(connection, state, updateState);
const loadEditions = () =>
pullEditions(connection, updateState, state, state.metadata);
const loading = [
loadCreators().then(loadMetadata).then(loadEditions),
loadVaults(),
loadAuctions(),
loadMetaplex(),
];
await Promise.all(loading);
state.metadata = uniqWith(
state.metadata,
(a: ParsedAccount<Metadata>, b: ParsedAccount<Metadata>) =>
a.pubkey === b.pubkey,
);
return state;
}
Example #12
Source File: RenameProvider.ts From language-tools with MIT License | 5 votes |
function unique<T>(array: T[]): T[] {
return uniqWith(array, isEqual);
}
Example #13
Source File: get-graphql-input-type.ts From prisma-nestjs-graphql with MIT License | 5 votes |
/**
* Find input type for graphql field decorator.
*/
export function getGraphqlInputType(
inputTypes: DMMF.SchemaArgInputType[],
pattern?: string,
) {
let result: DMMF.SchemaArgInputType | undefined;
inputTypes = inputTypes.filter(t => !['null', 'Null'].includes(String(t.type)));
inputTypes = uniqWith(inputTypes, isEqual);
if (inputTypes.length === 1) {
return inputTypes[0];
}
const countTypes = countBy(inputTypes, x => x.location);
const isOneType = Object.keys(countTypes).length === 1;
if (isOneType) {
result = inputTypes.find(x => x.isList);
if (result) {
return result;
}
}
if (pattern) {
if (pattern.startsWith('matcher:') || pattern.startsWith('match:')) {
const { 1: patternValue } = pattern.split(':', 2);
const isMatch = outmatch(patternValue, { separator: false });
result = inputTypes.find(x => isMatch(String(x.type)));
if (result) {
return result;
}
}
result = inputTypes.find(x => String(x.type).includes(pattern));
if (result) {
return result;
}
}
result = inputTypes.find(x => x.location === 'inputObjectTypes');
if (result) {
return result;
}
if (
countTypes.enumTypes &&
countTypes.scalar &&
inputTypes.some(x => x.type === 'Json' && x.location === 'scalar')
) {
result = inputTypes.find(x => x.type === 'Json' && x.location === 'scalar');
if (result) {
return result;
}
}
throw new TypeError(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Cannot get matching input type from ${
inputTypes.map(x => x.type).join(', ') || 'zero length inputTypes'
}`,
);
}
Example #14
Source File: build-gird-hierarchy.ts From S2 with MIT License | 4 votes |
buildGridHierarchy = (params: GridHeaderParams) => {
const {
addTotalMeasureInTotal,
addMeasureInTotalQuery,
parentNode,
currentField,
fields,
facetCfg,
hierarchy,
} = params;
const index = fields.indexOf(currentField);
const { dataSet, values, spreadsheet } = facetCfg;
const fieldValues: FieldValue[] = [];
let query = {};
if (parentNode.isTotals) {
// add total measures
if (addTotalMeasureInTotal) {
query = getDimsCondition(parentNode.parent, true);
// add total measures
fieldValues.push(...values.map((v) => new TotalMeasure(v)));
}
} else {
// field(dimension)'s all values
query = getDimsCondition(parentNode, true);
const dimValues = dataSet.getDimensionValues(currentField, query);
const arrangedValues = layoutArrange(
dimValues,
facetCfg,
parentNode,
currentField,
);
fieldValues.push(...(arrangedValues || []));
// add skeleton for empty data
const fieldName = dataSet.getFieldName(currentField);
if (isEmpty(fieldValues)) {
if (currentField === EXTRA_FIELD) {
fieldValues.push(...dataSet.fields?.values);
} else {
fieldValues.push(fieldName);
}
}
// hide measure in columns
hideMeasureColumn(fieldValues, currentField, facetCfg);
// add totals if needed
addTotals({
currentField,
lastField: fields[index - 1],
isFirstField: index === 0,
fieldValues,
spreadsheet,
});
}
const hiddenColumnsDetail = spreadsheet.store.get('hiddenColumnsDetail');
const isEqualValueLeafNode =
uniqWith(spreadsheet.getColumnLeafNodes(), (prev, next) => {
return prev.value === next.value;
}).length === 1;
const displayFieldValues = fieldValues.filter((value) => {
// 去除多余的节点
if (isUndefined(value)) {
return false;
}
if (isEmpty(hiddenColumnsDetail)) {
return true;
}
return hiddenColumnsDetail.every((detail) => {
return detail.hideColumnNodes.every((node) => {
// 1. 有数值字段 (hideMeasureColumn: false) 隐藏父节点
// 2. 多列头场景(数值挂列头, 隐藏数值列头, 自定义目录多指标等) 叶子节点是数值, 叶子节点的文本内容都一样, 需要额外比较父级节点的id是否相同, 确定到底隐藏哪一列
// 3. 自定义虚拟指标列 (列头内容未知)
const isMeasureField = node.field === EXTRA_FIELD;
const isCustomColumnField = node.field === EXTRA_COLUMN_FIELD;
if (isMeasureField || isCustomColumnField || isEqualValueLeafNode) {
return (
node.parent.id !== parentNode.id && node.parent.value !== value
);
}
// 没有数值字段 (hideMeasureColumn: true) 隐藏自己即可
return node.value !== value;
});
});
});
generateHeaderNodes({
currentField,
fields,
fieldValues: displayFieldValues,
facetCfg,
hierarchy,
parentNode,
level: index,
query,
addMeasureInTotalQuery,
addTotalMeasureInTotal,
});
}
Example #15
Source File: api-view.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
ApiView = ({ dataSource, onChangeVersion, deprecated, specProtocol }: IProps) => {
const params = routeInfoStore.useStore((s) => s.params);
const [hasAccess, accessDetail] = apiMarketStore.useStore((s) => [
s.assetVersionDetail.hasAccess,
s.assetVersionDetail.access,
]);
const [{ apiData, currentApi, testModalVisible, authModal, authed }, updater, update] = useUpdate<IState>({
apiData: {},
currentApi: '',
testModalVisible: false,
authModal: false,
authed: false,
});
React.useEffect(() => {
if (dataSource.spec) {
SwaggerParser.dereference(
cloneDeep(dataSource.spec),
{},
(err: Error | null, data: OpenAPI.Document | undefined) => {
if (err) {
message.error(i18n.t('default:failed to parse API description document'));
throw err;
}
updater.apiData((data || {}) as ApiData);
},
);
}
return () => {
updater.apiData({} as ApiData);
};
}, [dataSource.spec, updater]);
const getAuthInfo = React.useCallback(() => {
const authInfo = JSON.parse(sessionStorage.getItem(`asset-${params.assetID}`) || '{}');
return authInfo[`version-${params.versionID}`];
}, [params.assetID, params.versionID]);
React.useEffect(() => {
const authInfo = getAuthInfo();
let isAuthed = !isEmpty(authInfo);
if (isAuthed && accessDetail.authentication === authenticationMap['sign-auth'].value) {
isAuthed = !!authInfo.clientSecret;
}
updater.authed(isAuthed);
}, [getAuthInfo, updater, accessDetail.authentication]);
React.useEffect(() => {
return () => {
sessionStorage.removeItem(`asset-${params.assetID}`);
};
}, [params.assetID]);
const fullingUrl = React.useCallback(
(path: string) => {
let url = path;
if (apiData.basePath && !['', '/'].includes(apiData.basePath)) {
url = apiData.basePath + path;
}
return url;
},
[apiData.basePath],
);
const [tagMap, apiMap] = React.useMemo(() => {
const _tagMap = {} as TagMap;
const _apiMap = {} as { [K: string]: ApiMapItem };
if (isEmpty(apiData.paths)) {
return [{}, {}];
}
map(apiData.paths, (methodMap, path) => {
const _path = fullingUrl(path);
const httpRequests = pick(methodMap, HTTP_METHODS);
const restParams = omit(methodMap, HTTP_METHODS);
map(httpRequests, (api, method) => {
const parameters = uniqWith(
[...(api.parameters || [])].concat(restParams.parameters || []),
(a, b) => a.in === b.in && a.name === b.name,
);
const item: ApiMapItem = {
_method: method,
_path,
...api,
...restParams,
parameters,
};
map(api.tags || ['OTHER'], (tagName) => {
if (_tagMap[tagName]) {
_tagMap[tagName].push(item);
} else {
_tagMap[tagName] = [];
_tagMap[tagName].push(item);
}
});
_apiMap[method + _path] = item;
});
});
return [_tagMap, _apiMap];
}, [apiData.paths, fullingUrl]);
const handleChange = React.useCallback(
(key: string) => {
updater.currentApi(key);
},
[updater],
);
const handleShowTest = () => {
if (!authed) {
message.error(i18n.t('please authenticate first'));
return;
}
updater.testModalVisible(true);
};
const handleOk = (data: AutoInfo) => {
const authInfo = JSON.parse(sessionStorage.getItem(`asset-${params.assetID}`) || '{}');
authInfo[`version-${params.versionID}`] = data;
sessionStorage.setItem(`asset-${params.assetID}`, JSON.stringify(authInfo));
update({
authed: true,
authModal: false,
});
};
const testButton = hasAccess ? (
<>
<Button onClick={handleShowTest}>{i18n.t('test')}</Button>
{
<Button
className="ml-2"
onClick={() => {
updater.authModal(true);
}}
>
{authed ? i18n.t('recertification') : i18n.t('authentication')}
</Button>
}
</>
) : null;
const fieldsList: IFormItem[] = [
{
label: 'clientID',
name: 'clientID',
},
...insertWhen(accessDetail.authentication === authenticationMap['sign-auth'].value, [
{
label: 'clientSecret',
name: 'clientSecret',
getComp: () => <Input.Password />,
},
]),
];
const currentApiSource = apiMap[currentApi] || {};
const parametersMap: Dictionary<any[]> = groupBy(currentApiSource.parameters, 'in');
if (specProtocol && specProtocol.includes('oas3')) {
Object.assign(parametersMap, convertToOpenApi2(currentApiSource));
}
const autoInfo = getAuthInfo();
return (
<div className="apis-view flex justify-between items-center flex-1">
<div className="apis-view-left">
<ApiMenu list={tagMap} onChange={handleChange} onChangeVersion={onChangeVersion} />
</div>
<div className="apis-view-right">
{deprecated ? (
<ErdaAlert className="mb-4" type="warning" message={i18n.t('the current version is deprecated')} />
) : null}
<ApiDetail key={currentApi} dataSource={currentApiSource} extra={testButton} specProtocol={specProtocol} />
</div>
<TestModal
key={`${currentApiSource._method}${currentApiSource._path}`}
visible={testModalVisible}
onCancel={() => {
updater.testModalVisible(false);
}}
dataSource={{
autoInfo,
basePath: apiData.basePath,
url: currentApiSource._path,
method: currentApiSource._method?.toUpperCase(),
requestScheme: parametersMap,
host: apiData.host,
protocol: apiData.schemes?.includes('https') ? 'https' : 'http',
}}
/>
<FormModal
title={i18n.t('authentication')}
visible={authModal}
fieldsList={fieldsList}
onCancel={() => {
updater.authModal(false);
}}
formData={getAuthInfo()}
onOk={handleOk}
/>
</div>
);
}
Example #16
Source File: index.tsx From dashboard with Apache License 2.0 | 4 votes |
ContactWayList: React.FC = () => {
const [currentGroup, setCurrentGroup] = useState<ContactWayGroupItem>({});
const [itemDetailVisible, setItemDetailVisible] = useState(false);
const [currentItem, setCurrentItem] = useState<ContactWayItem>({});
const [selectedItems, setSelectedItems] = useState<ContactWayItem[]>([]);
const [filterGroupID, setFilterGroupID] = useState('0');
const [groupItems, setGroupItems] = useState<ContactWayGroupItem[]>([]);
const [groupItemsTimestamp, setGroupItemsTimestamp] = useState(Date.now);
const [createGroupVisible, setCreateGroupVisible] = useState(false);
const [batchUpdateVisible, setBatchUpdateVisible] = useState(false);
const [editGroupVisible, setEditGroupVisible] = useState(false);
const [allStaffs, setAllStaffs] = useState<StaffOption[]>([]);
const actionRef = useRef<ActionType>();
function showDeleteGroupConfirm(item: ContactWayGroupItem) {
Modal.confirm({
title: `删除分组`,
content: `是否确认删除「${item.name}」分组?`,
// icon: <ExclamationCircleOutlined/>,
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk() {
return HandleRequest({ids: [item.id]}, DeleteGroup, () => {
setGroupItemsTimestamp(Date.now);
});
},
});
}
useEffect(() => {
QuerySimpleStaffs({page_size: 5000}).then((res) => {
if (res.code === 0) {
setAllStaffs(
res?.data?.items?.map((item: SimpleStaffInterface) => {
return {
label: item.name,
value: item.ext_id,
...item,
};
}) || [],
);
} else {
message.error(res.message);
}
});
}, []);
useEffect(() => {
QueryGroup({page_size: 1000, sort_field: 'sort_weight', sort_type: 'asc'})
.then((resp) => {
if (resp && resp.data && resp.data.items) {
setGroupItems(resp.data.items);
}
})
.catch((err) => {
message.error(err);
});
}, [groupItemsTimestamp]);
const columns: ProColumns<ContactWayItem>[] = [
{
title: 'ID',
dataIndex: 'id',
valueType: 'text',
hideInTable: true,
hideInSearch: true,
fixed:'left',
},
{
title: '渠道码',
dataIndex: 'qr_code',
valueType: 'image',
hideInSearch: true,
width: 80,
fixed:'left',
render: (dom, item) => {
return (
<div className={'qrcodeWrapper'}>
<img
src={item.qr_code}
onClick={() => {
setItemDetailVisible(true);
setCurrentItem(item);
}}
className={'qrcode clickable'}
alt={item.name}
/>
</div>
);
},
},
{
title: '名称',
dataIndex: 'name',
valueType: 'text',
fixed:'left',
},
{
title: '使用员工',
dataIndex: 'staffs',
valueType: 'text',
hideInSearch: true,
width: 210,
render: (_, item) => {
let staffs: any[] = [];
item.schedules?.forEach((schedule) => {
if (schedule.staffs) {
staffs = [...staffs, ...schedule.staffs];
}
});
if (item.schedule_enable === True) {
staffs = uniqWith(staffs, (a, b) => a.ext_staff_id === b.ext_staff_id);
return <CollapsedStaffs limit={2} staffs={staffs}/>;
}
return <CollapsedStaffs limit={2} staffs={item.staffs}/>;
},
},
{
title: '使用员工',
dataIndex: 'ext_staff_ids',
valueType: 'text',
hideInTable: true,
renderFormItem: () => {
return <StaffTreeSelect options={allStaffs}/>;
},
},
{
title: '备份员工',
dataIndex: 'backup_staffs',
valueType: 'text',
hideInSearch: true,
width: 210,
render: (_, item) => {
return <CollapsedStaffs limit={2} staffs={item.backup_staffs}/>;
},
},
{
title: '标签',
dataIndex: 'customer_tags',
valueType: 'text',
ellipsis: true,
hideInSearch: true,
width: 210,
render: (_, item) => {
return <CollapsedTags limit={3} tags={item.customer_tags}/>;
},
},
{
title: '添加人次',
dataIndex: 'add_customer_count',
valueType: 'digit',
hideInSearch: true,
sorter: true,
showSorterTooltip: false,
width: 120,
tooltip: '统计添加渠道码的人次,若客户重复添加将会记录多条数据',
},
{
title: '创建时间',
dataIndex: 'created_at',
valueType: 'dateRange',
sorter: true,
filtered: true,
render: (dom, item) => {
return (
<div
dangerouslySetInnerHTML={{
__html: moment(item.created_at).format('YYYY-MM-DD HH:mm').split(' ').join('<br />'),
}}
/>
);
},
},
{
title: '操作',
width: 180,
valueType: 'option',
render: (_, item) => [
<a
key='detail'
onClick={() => {
setItemDetailVisible(true);
setCurrentItem(item);
}}
>
详情
</a>,
<a
key='download'
onClick={() => {
if (item?.qr_code) {
FileSaver.saveAs(item?.qr_code, `${item.name}.png`);
}
}}
>
下载
</a>,
<Dropdown
key='more'
overlay={
<Menu>
<Menu.Item
key='edit'
onClick={() => {
history.push(`/staff-admin/customer-growth/contact-way/edit?id=${item.id}`);
}}
>
修改
</Menu.Item>
<Menu.Item
key='copy'
onClick={() => {
history.push(`/staff-admin/customer-growth/contact-way/copy?id=${item.id}`);
}}
>
复制
</Menu.Item>
{item.ext_creator_id === localStorage.getItem(LSExtStaffAdminID) && (
<Menu.Item
key='delete'
onClick={() => {
Modal.confirm({
title: `删除渠道码`,
content: `是否确认删除「${item.name}」渠道码?`,
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk() {
return HandleRequest({ids: [item.id]}, Delete, () => {
actionRef.current?.clearSelected?.();
actionRef.current?.reload?.();
});
},
});
}}
>删除</Menu.Item>
)}
</Menu>
}
trigger={['hover']}
>
<a style={{display: 'flex', alignItems: 'center'}}>
编辑
<CaretDownOutlined style={{fontSize: '8px', marginLeft: '3px'}}/>
</a>
</Dropdown>,
],
},
];
// @ts-ignore
// @ts-ignore
return (
<PageContainer
fixedHeader
header={{
title: '渠道活码列表',
subTitle: (
<a
target={'_blank'}
className={styles.tipsLink}
// href={'https://www.openscrm.cn/wiki/contact-way'}
>
什么是渠道活码?
</a>
),
}}
extra={[
<Button
key='create'
type='primary'
icon={<PlusOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
onClick={() => {
history.push('/staff-admin/customer-growth/contact-way/create');
}}
>
新建活码
</Button>,
]}
>
<ProTable<ContactWayItem>
actionRef={actionRef}
className={'table'}
scroll={{x: 'max-content'}}
columns={columns}
rowKey='id'
pagination={{
pageSizeOptions: ['5', '10', '20', '50', '100'],
pageSize: 5,
}}
toolBarRender={false}
bordered={false}
tableAlertRender={false}
rowSelection={{
onChange: (_, items) => {
setSelectedItems(items);
},
}}
tableRender={(_, dom) => (
<div className={styles.mixedTable}>
<div className={styles.leftPart}>
<div className={styles.header}>
<Button
key='1'
className={styles.button}
type='text'
onClick={() => setCreateGroupVisible(true)}
icon={<PlusSquareFilled style={{color: 'rgb(154,173,193)', fontSize: 15}}/>}
>
新建分组
</Button>
</div>
<Menu
onSelect={(e) => {
setFilterGroupID(e.key as string);
}}
defaultSelectedKeys={['0']}
mode='inline'
className={styles.menuList}
>
<Menu.Item
icon={<FolderFilled style={{fontSize: '16px', color: '#138af8'}}/>}
key='0'
>
全部
</Menu.Item>
{groupItems.map((item) => (
<Menu.Item
icon={<FolderFilled style={{fontSize: '16px', color: '#138af8'}}/>}
key={item.id}
>
<div className={styles.menuItem}>
{item.name}
<span className={styles.count}
style={{marginRight: item.is_default === True ? 16 : 0}}>{item.count}</span>
</div>
{item.is_default === False && (
<Dropdown
className={'more-actions'}
overlay={
<Menu
onClick={(e) => {
e.domEvent.preventDefault();
e.domEvent.stopPropagation();
}}
>
<Menu.Item
onClick={() => {
setCurrentGroup(item);
setEditGroupVisible(true);
}}
key='edit'
>
修改名称
</Menu.Item>
<Menu.Item
onClick={() => {
showDeleteGroupConfirm(item);
}}
key='delete'
>
删除分组
</Menu.Item>
</Menu>
}
trigger={['hover']}
>
<MoreOutlined style={{color: '#9b9b9b', fontSize: 18}}/>
</Dropdown>
)}
</Menu.Item>
))}
</Menu>
</div>
<div className={styles.rightPart}>
<div className={styles.tableWrap}>{dom}</div>
</div>
</div>
)}
params={{
group_id: filterGroupID !== '0' ? filterGroupID : '',
}}
request={async (params, sort, filter) => {
return ProTableRequestAdapter(params, sort, filter, Query);
}}
dateFormatter='string'
/>
{selectedItems?.length > 0 && (
// 底部选中条目菜单栏
<FooterToolbar>
<span>
已选择 <a style={{fontWeight: 600}}>{selectedItems.length}</a> 项
<span></span>
</span>
<Divider type='vertical'/>
<Button
type='link'
onClick={() => {
actionRef.current?.clearSelected?.();
}}
>
取消选择
</Button>
<Button onClick={() => setBatchUpdateVisible(true)}>批量分组</Button>
<Button
icon={<CloudDownloadOutlined/>}
type={'primary'}
onClick={() => {
Modal.confirm({
title: `批量下载渠道码`,
content: `是否批量下载所选「${selectedItems.length}」个渠道码?`,
okText: '下载',
cancelText: '取消',
onOk: async () => {
const zip = new JSZip();
// eslint-disable-next-line no-restricted-syntax
for (const item of selectedItems) {
if (item?.qr_code) {
// eslint-disable-next-line no-await-in-loop
const img = (await fetch(item?.qr_code)).blob();
zip.file(`${item.name}_${item.id}.png`, img);
}
}
const content = await zip.generateAsync({type: 'blob'});
FileSaver.saveAs(content, `渠道活码_${moment().format('YYYY_MM_DD')}.zip`);
actionRef.current?.clearSelected?.();
return true;
},
});
}}
>
批量下载
</Button>
<Button
icon={<DeleteOutlined/>}
onClick={async () => {
Modal.confirm({
title: `删除渠道码`,
content: `是否批量删除所选「${selectedItems.length}」个渠道码?`,
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk() {
return HandleRequest(
{ids: selectedItems.map((item) => item.id)},
Delete,
() => {
actionRef.current?.clearSelected?.();
actionRef.current?.reload?.();
},
);
},
});
}}
danger={true}
>
批量删除
</Button>
</FooterToolbar>
)}
<ModalForm
width={468}
className={'dialog from-item-label-100w'}
layout={'horizontal'}
visible={batchUpdateVisible}
onVisibleChange={setBatchUpdateVisible}
onFinish={async (values) => {
return await HandleRequest(
{ids: selectedItems.map((item) => item.id), ...values},
BatchUpdate,
() => {
actionRef.current?.clearSelected?.();
actionRef.current?.reload?.();
setGroupItemsTimestamp(Date.now);
},
);
}}
>
<h2 className='dialog-title'> 批量修改渠道码 </h2>
<ProFormSelect
// @ts-ignore
options={groupItems.map((groupItem) => {
return {key: groupItem.id, label: groupItem.name, value: groupItem.id};
})}
labelAlign={'left'}
name='group_id'
label='新分组'
placeholder='请选择分组'
rules={[
{
required: true,
message: '请选择新分组',
},
]}
/>
</ModalForm>
<ModalForm
width={400}
className={'dialog from-item-label-100w'}
layout={'horizontal'}
visible={createGroupVisible}
onVisibleChange={setCreateGroupVisible}
onFinish={async (params) =>
HandleRequest({...currentGroup, ...params}, CreateGroup, () => {
setGroupItemsTimestamp(Date.now);
})
}
>
<h2 className='dialog-title'> 新建分组 </h2>
<ProFormText
name='name'
label='分组名称'
tooltip='最长为 24 个汉字'
placeholder='请输入分组名称'
rules={[
{
required: true,
message: '请填写分组名称',
},
]}
/>
</ModalForm>
<ModalForm
className={'dialog from-item-label-100w'}
layout={'horizontal'}
width={'500px'}
visible={editGroupVisible}
onVisibleChange={setEditGroupVisible}
onFinish={async (params) =>
HandleRequest({...currentGroup, ...params}, UpdateGroup, () => {
setGroupItemsTimestamp(Date.now);
})
}
>
<h2 className='dialog-title'> 修改名称 </h2>
<ProFormText
colon={true}
name='name'
label='分组名称'
tooltip='最长为 24 个汉字'
placeholder='请输入分组名称'
initialValue={currentGroup.name}
rules={[
{
required: true,
message: '请填写分组名称',
},
]}
/>
</ModalForm>
<Modal
className={styles.detailDialog}
width={'800px'}
visible={itemDetailVisible}
onCancel={() => setItemDetailVisible(false)}
footer={null}
>
<h2 className='dialog-title' style={{textAlign: "center", fontSize: 19}}> 渠道码详情 </h2>
<Row>
<Col span={8} className={styles.leftPart}>
<img src={currentItem.qr_code}/>
<h3>{currentItem.name}</h3>
<Button
type={'primary'}
onClick={() => {
if (currentItem?.qr_code) {
FileSaver.saveAs(currentItem?.qr_code, `${currentItem.name}.png`);
}
}}
>
下载渠道码
</Button>
<Button
onClick={() => {
history.push(
`/staff-admin/customer-growth/contact-way/edit?id=${currentItem.id}`,
);
}}
>
修改
</Button>
</Col>
<Col span={16} className={styles.rightPart}>
<div className={styles.section}>
<div className={styles.titleWrapper}>
<div className={styles.divider}/>
<span className={styles.title}>基本设置</span>
</div>
<div className={styles.formItem}>
<span className={styles.title}>创建时间:</span>
<span className='date'>
{moment(currentItem.created_at).format('YYYY-MM-DD HH:mm')}
</span>
</div>
<div className={styles.formItem}>
<span className={styles.title}>绑定员工:</span>
{currentItem.staffs?.map((staff) => (
<Tag
key={staff.id}
className={styles.staffTag}
style={{opacity: staff.online === False ? '0.5' : '1'}}
>
<img className={styles.icon} src={staff.avatar_url} alt={staff.name}/>
<span className={styles.text}>{staff.name}</span>
</Tag>
))}
</div>
<div className={styles.formItem}>
<span className={styles.title}>备份员工:</span>
{currentItem.backup_staffs?.map((staff) => (
<Tag
key={staff.id}
className={styles.staffTag}
style={{opacity: staff.online === False ? '0.5' : '1'}}
>
<img className={styles.icon} src={staff.avatar_url} alt={staff.name}/>
<span className={styles.text}>{staff.name}</span>
</Tag>
))}
</div>
<p className={styles.formItem}>
<span className={styles.title}>自动通过好友:</span>
{currentItem.auto_skip_verify_enable === True && (
<span>
{currentItem.skip_verify_start_time && '~'}
{currentItem.skip_verify_end_time}自动通过
</span>
)}
{currentItem.auto_skip_verify_enable === False && <span>未开启</span>}
</p>
<p className={styles.formItem}>
<span className={styles.title}>客户标签:</span>
{currentItem.customer_tags?.map((tag) => (
<Tag key={tag.id} className={styles.staffTag}>
<span className={styles.text}>{tag.name}</span>
</Tag>
))}
</p>
</div>
</Col>
</Row>
</Modal>
</PageContainer>
);
}